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 }