205 lines
4.9 KiB
Go
205 lines
4.9 KiB
Go
package builtin
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/dop251/goja"
|
|
)
|
|
|
|
func convertJSValueToGo(vm *goja.Runtime, jsValue goja.Value, targetType reflect.Type) (any, error) {
|
|
if goja.IsNull(jsValue) {
|
|
if targetType.Kind() == reflect.Pointer || targetType.Kind() == reflect.Map {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("cannot convert null/undefined to %v", targetType)
|
|
}
|
|
|
|
if goja.IsUndefined(jsValue) {
|
|
if targetType.Kind() == reflect.Pointer || targetType.Kind() == reflect.Map {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("cannot convert null/undefined to %v", targetType)
|
|
}
|
|
|
|
switch targetType.Kind() {
|
|
case reflect.String:
|
|
return jsValue.String(), nil
|
|
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
n, ok := jsValue.Export().(int64)
|
|
if !ok {
|
|
return nil, fmt.Errorf("expected int, got %T", jsValue.Export())
|
|
}
|
|
return reflect.ValueOf(n).Convert(targetType).Interface(), nil
|
|
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
n, ok := jsValue.Export().(int64)
|
|
if !ok {
|
|
return nil, fmt.Errorf("expected uint, got %T", jsValue.Export())
|
|
}
|
|
return reflect.ValueOf(uint(n)).Convert(targetType).Interface(), nil
|
|
|
|
case reflect.Float32, reflect.Float64:
|
|
n, ok := jsValue.Export().(float64)
|
|
if !ok {
|
|
return nil, fmt.Errorf("expected float, got %T", jsValue.Export())
|
|
}
|
|
return reflect.ValueOf(n).Convert(targetType).Interface(), nil
|
|
|
|
case reflect.Bool:
|
|
return jsValue.ToBoolean(), nil
|
|
|
|
case reflect.Interface:
|
|
return jsValue.Export(), nil
|
|
|
|
case reflect.Map:
|
|
if goja.IsUndefined(jsValue) || goja.IsNull(jsValue) {
|
|
return nil, nil
|
|
}
|
|
|
|
if targetType.Key().Kind() == reflect.String {
|
|
obj := jsValue.ToObject(vm)
|
|
if obj == nil {
|
|
return nil, fmt.Errorf("not an object")
|
|
}
|
|
|
|
if targetType.Elem().Kind() == reflect.Interface {
|
|
result := make(map[string]any)
|
|
for _, key := range obj.Keys() {
|
|
result[key] = obj.Get(key).Export()
|
|
}
|
|
return result, nil
|
|
} else if targetType.Elem().Kind() == reflect.String {
|
|
result := make(map[string]string)
|
|
for _, key := range obj.Keys() {
|
|
v := obj.Get(key)
|
|
result[key] = v.String()
|
|
}
|
|
return result, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("unsupported map type: %v", targetType)
|
|
|
|
case reflect.Struct:
|
|
obj := jsValue.ToObject(vm)
|
|
if obj == nil {
|
|
return nil, fmt.Errorf("not an object")
|
|
}
|
|
|
|
result := reflect.New(targetType).Elem()
|
|
for i := 0; i < targetType.NumField(); i++ {
|
|
field := targetType.Field(i)
|
|
fieldName := getFieldName(field)
|
|
|
|
jsField := obj.Get(fieldName)
|
|
|
|
var err error
|
|
var converted any
|
|
func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = nil
|
|
converted = nil
|
|
}
|
|
}()
|
|
converted, err = convertJSValueToGo(vm, jsField, field.Type)
|
|
}()
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("field %s: %v", fieldName, err)
|
|
}
|
|
|
|
if converted == nil {
|
|
if field.Type.Kind() == reflect.Pointer || field.Type.Kind() == reflect.Map {
|
|
continue
|
|
}
|
|
} else {
|
|
result.Field(i).Set(reflect.ValueOf(converted))
|
|
}
|
|
}
|
|
return result.Interface(), nil
|
|
|
|
case reflect.Pointer:
|
|
if goja.IsNull(jsValue) || goja.IsUndefined(jsValue) {
|
|
return nil, nil
|
|
}
|
|
|
|
elemType := targetType.Elem()
|
|
converted, err := convertJSValueToGo(vm, jsValue, elemType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ptr := reflect.New(elemType)
|
|
ptr.Elem().Set(reflect.ValueOf(converted))
|
|
return ptr.Interface(), nil
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unsupported type: %v", targetType)
|
|
}
|
|
}
|
|
|
|
func convertGoValueToJS(vm *goja.Runtime, goValue reflect.Value) goja.Value {
|
|
value := goValue.Interface()
|
|
|
|
switch v := value.(type) {
|
|
case string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool:
|
|
return vm.ToValue(v)
|
|
|
|
case error:
|
|
return vm.ToValue(v.Error())
|
|
|
|
case map[string]string:
|
|
obj := vm.NewObject()
|
|
for key, val := range v {
|
|
_ = obj.Set(key, val)
|
|
}
|
|
return obj
|
|
|
|
case map[string]any:
|
|
obj := vm.NewObject()
|
|
for key, val := range v {
|
|
_ = obj.Set(key, convertGoValueToJS(vm, reflect.ValueOf(val)))
|
|
}
|
|
return obj
|
|
|
|
case []any:
|
|
arr := make([]goja.Value, len(v))
|
|
for i, item := range v {
|
|
arr[i] = convertGoValueToJS(vm, reflect.ValueOf(item))
|
|
}
|
|
return vm.ToValue(arr)
|
|
|
|
default:
|
|
if goValue.Kind() == reflect.Pointer {
|
|
if goValue.IsNil() {
|
|
return goja.Null()
|
|
}
|
|
return convertGoValueToJS(vm, goValue.Elem())
|
|
}
|
|
|
|
if goValue.Kind() == reflect.Struct {
|
|
obj := vm.NewObject()
|
|
for i := 0; i < goValue.NumField(); i++ {
|
|
field := goValue.Type().Field(i)
|
|
fieldName := getFieldName(field)
|
|
_ = obj.Set(fieldName, convertGoValueToJS(vm, goValue.Field(i)))
|
|
}
|
|
return obj
|
|
}
|
|
|
|
return vm.ToValue(v)
|
|
}
|
|
}
|
|
|
|
func getFieldName(field reflect.StructField) string {
|
|
jsonTag := field.Tag.Get("json")
|
|
if jsonTag != "" && jsonTag != "-" {
|
|
name, _, _ := strings.Cut(jsonTag, ",")
|
|
return name
|
|
}
|
|
return field.Name
|
|
}
|