Files
napi-go/js.go
Matheus Sampaio Queiroga 379bd75a8e Refactor JS value to Go type conversion
Simplify `valueFrom` logic, particularly for `any` interface,
structs, maps, and slices. Add support for TextUnmarshaler and
JsonUnmarshaler interfaces.

Correct struct tag parsing in `valueOf` and argument value creation in
`goValuesInFunc`.

Add `goFuncConv` example demonstrating Go struct/function conversion.
2025-04-28 19:15:59 -03:00

543 lines
14 KiB
Go

package napi
import (
"encoding"
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
"time"
)
const propertiesTagName = "napi"
// Convert go types to valid NAPI, if not conpatible return Undefined.
func ValueOf(env EnvType, value any) (napiValue ValueType, err error) {
return valueOf(env, reflect.ValueOf(value))
}
// Convert NAPI value to Go values
func ValueFrom(napiValue ValueType, v any) error {
ptr := reflect.ValueOf(v)
if ptr.Kind() != reflect.Pointer {
return fmt.Errorf("require point to convert napi value to go value")
}
return valueFrom(napiValue, ptr.Elem())
}
// Convert go types to valid NAPI, if not conpatible return Undefined.
func valueOf(env EnvType, ptr reflect.Value) (napiValue ValueType, err error) {
defer func(err *error) {
if err2 := recover(); err2 != nil {
switch v := err2.(type) {
case error:
*err = v
default:
*err = fmt.Errorf("panic recover: %s", err2)
}
}
}(&err)
ptrType := ptr.Type()
if ptrType.ConvertibleTo(reflect.TypeFor[ValueType]()) {
if ptr.IsValid() {
return ptr.Interface().(ValueType), nil
}
return nil, nil
} else if !ptr.IsValid() {
return env.Undefined()
} else if !ptr.IsZero() && ptr.CanInterface() { // Marshalers
switch v := ptr.Interface().(type) {
case time.Time:
return CreateDate(env, v)
case encoding.TextMarshaler:
data, err := v.MarshalText()
if err != nil {
return nil, err
}
return CreateString(env, string(data))
case json.Marshaler:
var pointData any
data, err := v.MarshalJSON()
if err != nil {
return nil, err
} else if err = json.Unmarshal(data, &pointData); err != nil {
return nil, err
}
return ValueOf(env, pointData)
}
}
switch ptrType.Kind() {
case reflect.Pointer:
return valueOf(env, ptr.Elem())
case reflect.String:
return CreateString(env, ptr.String())
case reflect.Bool:
return CreateBoolean(env, ptr.Bool())
case reflect.Int, reflect.Uint, reflect.Int32, reflect.Uint32, reflect.Float32, reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16:
return CreateNumber(env, ptr.Int())
case reflect.Float64:
return CreateNumber(env, ptr.Float())
case reflect.Int64, reflect.Uint64:
return CreateBigint(env, ptr.Int())
case reflect.Func:
return funcOf(env, ptr)
case reflect.Slice, reflect.Array:
arr, err := CreateArray(env, ptr.Len())
if err != nil {
return nil, err
}
for index := range ptr.Len() {
value, err := valueOf(env, ptr.Index(index))
if err != nil {
return arr, err
} else if err = arr.Set(index, value); err != nil {
return arr, err
}
}
return arr, nil
case reflect.Struct:
obj, err := CreateObject(env)
if err != nil {
return nil, err
}
for keyIndex := range ptrType.NumField() {
field, fieldType := ptr.Field(keyIndex), ptrType.Field(keyIndex)
if !fieldType.IsExported() || fieldType.Tag.Get(propertiesTagName) == "-" {
continue
}
value, err := valueOf(env, field)
if err != nil {
return obj, err
}
typeof, err := value.Type()
if err != nil {
return nil, err
}
var keyNamed string
switch v := strings.TrimSpace(fieldType.Tag.Get(propertiesTagName)); v {
case "":
keyNamed = fieldType.Name
default:
keyNamed = v
if strings.Count(v, ",") > 0 {
fields := strings.SplitN(v, ",", 2)
keyNamed = fields[0]
switch fields[1] {
case "omitempty":
switch typeof {
case TypeUndefined, TypeNull, TypeUnkown:
continue
case TypeString:
str, err := ToString(value).Utf8Value()
if err != nil {
return nil, err
} else if str == "" {
continue
}
}
case "omitzero":
switch typeof {
case TypeUndefined, TypeNull, TypeUnkown:
continue
case TypeDate:
value, err := ToDate(value).Time()
if err != nil {
return nil, err
} else if value.Unix() == 0 {
continue
}
case TypeBigInt:
value, err := ToBigint(value).Int64()
if err != nil {
return nil, err
} else if value == 0 {
continue
}
case TypeNumber:
value, err := ToNumber(value).Int()
if err != nil {
return nil, err
} else if value == 0 {
continue
}
case TypeArray:
value, err := ToArray(value).Length()
if err != nil {
return nil, err
} else if value == 0 {
continue
}
}
}
}
}
if err = obj.Set(keyNamed, value); err != nil {
return obj, err
}
}
return obj, nil
case reflect.Map:
obj, err := CreateObject(env)
if err != nil {
return nil, err
}
for ptrKey, ptrValue := range ptr.Seq2() {
key, err := valueOf(env, ptrKey)
if err != nil {
return nil, err
}
value, err := valueOf(env, ptrValue)
if err != nil {
return nil, err
} else if err = obj.SetWithValue(key, value); err != nil {
return nil, err
}
}
return obj, nil
case reflect.Interface:
if ptr.IsValid() {
if ptr.IsNil() {
return env.Null()
} else if ptr.CanInterface() {
return valueOf(env, reflect.ValueOf(ptr.Interface()))
}
}
}
return env.Undefined()
}
// Convert javascript value to go typed value
func valueFrom(jsValue ValueType, ptr reflect.Value) error {
typeOf, err := jsValue.Type()
if err != nil {
return err
}
ptrType := ptr.Type()
if ptrType.ConvertibleTo(reflect.TypeFor[ValueType]()) {
switch typeOf {
case TypeUndefined:
und, err := jsValue.Env().Undefined()
if err != nil {
return err
}
ptr.Set(reflect.ValueOf(und))
case TypeNull:
null, err := jsValue.Env().Null()
if err != nil {
return err
}
ptr.Set(reflect.ValueOf(null))
case TypeBoolean:
ptr.Set(reflect.ValueOf(ToBoolean(jsValue)))
case TypeNumber:
ptr.Set(reflect.ValueOf(ToNumber(jsValue)))
case TypeBigInt:
ptr.Set(reflect.ValueOf(ToBigint(jsValue)))
case TypeString:
ptr.Set(reflect.ValueOf(ToString(jsValue)))
case TypeSymbol:
// ptr.Set(reflect.ValueOf(ToString(jsValue)))
case TypeObject:
ptr.Set(reflect.ValueOf(ToObject(jsValue)))
case TypeFunction:
ptr.Set(reflect.ValueOf(ToFunction(jsValue)))
case TypeExternal:
ptr.Set(reflect.ValueOf(ToExternal(jsValue)))
case TypeTypedArray:
ptr.Set(reflect.ValueOf(ToTypedArray(jsValue)))
case TypePromise:
// ptr.Set(reflect.ValueOf(ToFunction(jsValue)))
case TypeBuffer:
ptr.Set(reflect.ValueOf(ToBuffer(jsValue)))
case TypeDate:
ptr.Set(reflect.ValueOf(ToDate(jsValue)))
case TypeArray:
ptr.Set(reflect.ValueOf(ToArray(jsValue)))
case TypeArrayBuffer:
ptr.Set(reflect.ValueOf(ToArrayBuffer(jsValue)))
case TypeDataView:
ptr.Set(reflect.ValueOf(ToDataView(jsValue)))
case TypeError:
ptr.Set(reflect.ValueOf(ToError(jsValue)))
}
return nil
}
switch ptrType.Kind() {
case reflect.Pointer:
return valueFrom(jsValue, ptr.Elem())
case reflect.Interface:
if !ptr.CanSet() || ptrType != reflect.TypeFor[any]() {
break
}
switch typeOf {
case TypeNull, TypeUndefined, TypeUnkown:
ptr.Set(reflect.Zero(ptrType))
return nil
case TypeBoolean:
valueOf, err := ToBoolean(jsValue).Value()
if err != nil {
return err
}
ptr.Set(reflect.ValueOf(valueOf))
case TypeNumber:
numberValue, err := ToNumber(jsValue).Float()
if err != nil {
return err
}
ptr.Set(reflect.ValueOf(numberValue))
case TypeBigInt:
numberValue, err := ToBigint(jsValue).Int64()
if err != nil {
return err
}
ptr.Set(reflect.ValueOf(numberValue))
case TypeString:
str, err := ToString(jsValue).Utf8Value()
if err != nil {
return err
}
ptr.Set(reflect.ValueOf(str))
case TypeDate:
timeDate, err := ToDate(jsValue).Time()
if err != nil {
return err
}
ptr.Set(reflect.ValueOf(timeDate))
case TypeArray:
napiArray := ToArray(jsValue)
size, err := napiArray.Length()
if err != nil {
return err
}
value := reflect.MakeSlice(reflect.SliceOf(ptrType), size, size)
for index := range size {
napiValue, err := napiArray.Get(index)
if err != nil {
return err
} else if err = valueFrom(napiValue, value.Index(index)); err != nil {
return err
}
}
ptr.Set(value)
case TypeBuffer:
buff, err := ToBuffer(jsValue).Data()
if err != nil {
return err
}
ptr.Set(reflect.ValueOf(buff))
case TypeObject:
obj := ToObject(jsValue)
goMap := reflect.MakeMap(reflect.MapOf(reflect.TypeFor[string](), reflect.TypeFor[any]()))
for keyName, value := range obj.Seq() {
valueOf := reflect.New(reflect.TypeFor[any]())
if err := valueFrom(value, valueOf); err != nil {
return err
}
goMap.SetMapIndex(reflect.ValueOf(keyName), valueOf)
}
ptr.Set(goMap)
case TypeFunction:
ptr.Set(reflect.ValueOf(ToFunction(jsValue)))
}
return nil
case reflect.String:
if typeOf != TypeString {
break
}
valueOf, err := ToString(jsValue).Utf8Value()
if err != nil {
return err
}
ptr.Set(reflect.ValueOf(valueOf))
return nil
case reflect.Bool:
if typeOf != TypeBoolean {
break
}
b, err := ToBoolean(jsValue).Value()
if err != nil {
return err
}
ptr.Set(reflect.ValueOf(b))
return nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
switch typeOf {
case TypeNumber:
b, err := ToNumber(jsValue).Int()
if err != nil {
return err
}
ptr.SetInt(b)
return nil
case TypeBigInt:
b, err := ToBigint(jsValue).Int64()
if err != nil {
return err
}
ptr.SetInt(b)
return nil
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
switch typeOf {
case TypeNumber:
b, err := ToNumber(jsValue).Int()
if err != nil {
return err
}
ptr.SetUint(uint64(b))
return nil
case TypeBigInt:
b, err := ToBigint(jsValue).Int64()
if err != nil {
return err
}
ptr.SetUint(uint64(b))
return nil
}
case reflect.Float32, reflect.Float64:
if typeOf != TypeNumber {
break
}
f, err := ToNumber(jsValue).Float()
if err != nil {
return err
}
ptr.SetFloat(f)
return nil
case reflect.Func, reflect.Chan:
return nil
case reflect.Slice:
if typeOf != TypeArray {
break
}
jsArr := ToArray(jsValue)
size, err := jsArr.Length()
if err != nil {
return err
}
ptr.Set(reflect.MakeSlice(ptrType, size, size))
for index := range size {
jsValue, err := jsArr.Get(index)
if err != nil {
return err
} else if err = valueFrom(jsValue, ptr.Index(index)); err != nil {
return err
}
}
case reflect.Map:
// Check if key is string, bool, int*, uint*, float*, else return error
switch ptrType.Key().Kind() {
case reflect.String:
case reflect.Bool:
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
case reflect.Float32, reflect.Float64:
default:
return fmt.Errorf("cannot set Object ket to %s", ptr.Kind())
}
goMap := reflect.MakeMap(ptrType)
obj := ToObject(jsValue)
for keyName, value := range obj.Seq() {
keySetValue := reflect.New(ptrType.Key()).Elem()
switch ptrType.Key().Kind() {
case reflect.String:
keySetValue.SetString(keyName)
case reflect.Bool:
boolV, err := strconv.ParseBool(keyName)
if err != nil {
return err
}
keySetValue.SetBool(boolV)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
intV, err := strconv.ParseInt(keyName, 10, 64)
if err != nil {
return err
}
keySetValue.SetInt(intV)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
intV, err := strconv.ParseUint(keyName, 10, 64)
if err != nil {
return err
}
keySetValue.SetUint(intV)
case reflect.Float32, reflect.Float64:
floatV, err := strconv.ParseFloat(keyName, 64)
if err != nil {
return err
}
keySetValue.SetFloat(floatV)
}
valueOf := reflect.New(ptrType.Elem()).Elem()
if err := valueFrom(value, valueOf); err != nil {
return err
}
goMap.SetMapIndex(keySetValue, valueOf)
}
ptr.Set(goMap)
return nil
case reflect.Struct:
switch typeOf {
case TypeString:
str, err := ToString(jsValue).Utf8Value()
if err != nil {
return err
}
switch {
case ptrType.ConvertibleTo(reflect.TypeFor[encoding.TextUnmarshaler]()):
return ptr.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(str))
case ptrType.ConvertibleTo(reflect.TypeFor[json.Unmarshaler]()):
var raw json.RawMessage
if err = json.Unmarshal([]byte(str), &raw); err == nil {
return ptr.Interface().(json.Unmarshaler).UnmarshalJSON(raw)
}
}
case TypeObject:
obj := ToObject(jsValue)
ptr.Set(reflect.New(ptrType).Elem())
for keyIndex := range ptrType.NumField() {
fieldType := ptrType.Field(keyIndex)
if !fieldType.IsExported() || fieldType.Tag.Get(propertiesTagName) == "-" {
continue
}
var keyName string
switch v := strings.TrimSpace(fieldType.Tag.Get(propertiesTagName)); v {
case "":
keyName = fieldType.Name
default:
keyName = v
if strings.Count(fieldType.Tag.Get(propertiesTagName), ",") > 0 {
fields := strings.SplitN(fieldType.Tag.Get(propertiesTagName), ",", 2)
keyName = fields[0]
}
}
if ok, _ := obj.Has(keyName); !ok {
continue
}
value, _ := obj.Get(keyName)
valueOf := reflect.New(fieldType.Type)
if err := valueFrom(value, valueOf); err != nil {
return err
}
ptr.Field(keyIndex).Set(valueOf.Elem())
}
return nil
}
}
return fmt.Errorf("cannot set %s, to %s", typeOf, ptr.Kind())
}