diff --git a/builtin/builtin.go b/builtin/builtin.go index 4375f994..b80b7796 100644 --- a/builtin/builtin.go +++ b/builtin/builtin.go @@ -443,17 +443,27 @@ var Builtins = []*Function{ Name: "fromJSON", Func: func(args ...any) (any, error) { var v any - jsonStr := args[0] - if strPtr, ok := jsonStr.(*string); ok { - jsonStr = *strPtr + var jsonStr string + + switch arg := args[0].(type) { + case string: + jsonStr = arg + case *string: + if arg == nil { + return nil, fmt.Errorf("nil string pointer") + } + jsonStr = *arg + default: + return nil, fmt.Errorf("expected string or *string, got %T", args[0]) } - err := json.Unmarshal([]byte(jsonStr.(string)), &v) + + err := json.Unmarshal([]byte(jsonStr), &v) if err != nil { return nil, err } return v, nil }, - Types: types(new(func(string) any)), + Types: types(new(func(string) any), new(func(*string) any)), }, { Name: "toBase64", diff --git a/internal/deref/deref.go b/internal/deref/deref.go index acdc8981..06c0900b 100644 --- a/internal/deref/deref.go +++ b/internal/deref/deref.go @@ -30,9 +30,28 @@ func Type(t reflect.Type) reflect.Type { if t == nil { return nil } + + // Preserve interface types immediately to maintain type information + // This handles both empty (interface{}) and non-empty (e.g., io.Reader) interfaces + if t.Kind() == reflect.Interface { + return t + } + + // Iteratively unwrap pointer types until we reach a non-pointer + // or encounter an interface type that needs preservation for t.Kind() == reflect.Ptr { t = t.Elem() + if t == nil { + return nil + } + // Stop unwrapping if we hit an interface type to preserve its type information + // This ensures interface method sets are not lost + if t.Kind() == reflect.Interface { + return t + } } + + // Return the final unwrapped type, which could be any non-pointer, non-interface type return t } diff --git a/internal/deref/deref_test.go b/internal/deref/deref_test.go index 5f812bee..d9e31da3 100644 --- a/internal/deref/deref_test.go +++ b/internal/deref/deref_test.go @@ -67,6 +67,23 @@ func TestType_nil(t *testing.T) { assert.Nil(t, deref.Type(nil)) } +func TestType_interface_wrapped_pointer(t *testing.T) { + t.Run("one level", func(t *testing.T) { + str := "hello" + var iface any = &str + dt := deref.Type(reflect.TypeOf(iface)) + assert.Equal(t, reflect.String, dt.Kind()) + }) + + t.Run("two levels", func(t *testing.T) { + str := "hello" + strPtr := &str + var iface any = &strPtr + dt := deref.Type(reflect.TypeOf(iface)) + assert.Equal(t, reflect.String, dt.Kind()) + }) +} + func TestValue(t *testing.T) { a := uint(42) b := &a