Skip to content

Commit

Permalink
supplement to PR #4107 (#4183)
Browse files Browse the repository at this point in the history
  • Loading branch information
wln32 authored Mar 5, 2025
2 parents 760799f + 4ac7359 commit 5feda53
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 26 deletions.
5 changes: 5 additions & 0 deletions util/gconv/gconv.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ var (
defaultConverter = converter.NewConverter()
)

// RegisterAnyConverterFunc registers custom type converting function for specified type.
func RegisterAnyConverterFunc(f AnyConvertFunc, types ...reflect.Type) {
defaultConverter.RegisterAnyConverterFunc(f, types...)
}

// NewConverter creates and returns management object for type converting.
func NewConverter() Converter {
return converter.NewConverter()
Expand Down
87 changes: 87 additions & 0 deletions util/gconv/gconv_z_unit_converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
package gconv_test

import (
"database/sql"
"fmt"
"reflect"
"testing"

"github.com/gogf/gf/v2/test/gtest"
Expand Down Expand Up @@ -84,3 +87,87 @@ func TestConvertWithRefer(t *testing.T) {
t.AssertNE(gconv.ConvertWithRefer("1.01", false), false)
})
}

func testAnyToMyInt(from any, to reflect.Value) error {
switch x := from.(type) {
case int:
to.SetInt(123456)
default:
return fmt.Errorf("unsupported type %T(%v)", x, x)
}
return nil
}

func testAnyToSqlNullType(_ any, to reflect.Value) error {
if to.Kind() != reflect.Ptr {
to = to.Addr()
}
return to.Interface().(sql.Scanner).Scan(123456)
}

func TestNewConverter(t *testing.T) {
type Dst[T any] struct {
A T
}
gtest.C(t, func(t *gtest.T) {
conv := gconv.NewConverter()
conv.RegisterAnyConverterFunc(testAnyToMyInt, reflect.TypeOf((*myInt)(nil)))
var dst Dst[myInt]
err := conv.Struct(map[string]any{
"a": 1200,
}, &dst, gconv.StructOption{})
t.AssertNil(err)
t.Assert(dst, Dst[myInt]{
A: 123456,
})
})
gtest.C(t, func(t *gtest.T) {
conv := gconv.NewConverter()
conv.RegisterAnyConverterFunc(testAnyToMyInt, reflect.TypeOf((myInt)(0)))
var dst Dst[*myInt]
err := conv.Struct(map[string]any{
"a": 1200,
}, &dst, gconv.StructOption{})
t.AssertNil(err)
t.Assert(*dst.A, 123456)
})

gtest.C(t, func(t *gtest.T) {
conv := gconv.NewConverter()
conv.RegisterAnyConverterFunc(testAnyToSqlNullType, reflect.TypeOf((*sql.Scanner)(nil)))
type sqlNullDst struct {
A sql.Null[int]
B sql.Null[float32]
C sql.NullInt64
D sql.NullString

E *sql.Null[int]
F *sql.Null[float32]
G *sql.NullInt64
H *sql.NullString
}
var dst sqlNullDst
err := conv.Struct(map[string]any{
"a": 12,
"b": 34,
"c": 56,
"d": "sqlNullString",
"e": 12,
"f": 34,
"g": 56,
"h": "sqlNullString",
}, &dst, gconv.StructOption{})
t.AssertNil(err)
t.Assert(dst, sqlNullDst{
A: sql.Null[int]{V: 123456, Valid: true},
B: sql.Null[float32]{V: 123456, Valid: true},
C: sql.NullInt64{Int64: 123456, Valid: true},
D: sql.NullString{String: "123456", Valid: true},

E: &sql.Null[int]{V: 123456, Valid: true},
F: &sql.Null[float32]{V: 123456, Valid: true},
G: &sql.NullInt64{Int64: 123456, Valid: true},
H: &sql.NullString{String: "123456", Valid: true},
})
})
}
34 changes: 34 additions & 0 deletions util/gconv/internal/structcache/structcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import (
"github.com/gogf/gf/v2/util/gconv/internal/localinterface"
)

type interfaceTypeConverter struct {
interfaceType reflect.Type
convertFunc AnyConvertFunc
}

// Converter is the configuration for type converting.
type Converter struct {
// map[reflect.Type]*CachedStructInfo
Expand All @@ -22,6 +27,10 @@ type Converter struct {
// anyToTypeConvertMap for custom type converting from any to its reflect.Value.
anyToTypeConvertMap map[reflect.Type]AnyConvertFunc

// interfaceToTypeConvertMap used for converting any interface type
// the reason why map is not used is because interface types cannot be instantiated
interfaceToTypeConvertMap []interfaceTypeConverter

// typeConverterFuncMarkMap is used to store whether field types are registered to custom conversions
typeConverterFuncMarkMap map[reflect.Type]struct{}
}
Expand All @@ -48,9 +57,34 @@ func (cf *Converter) MarkTypeConvertFunc(fieldType reflect.Type) {

// RegisterAnyConvertFunc registers custom type converting function for specified type.
func (cf *Converter) RegisterAnyConvertFunc(t reflect.Type, convertFunc AnyConvertFunc) {
if t == nil || convertFunc == nil {
panic("cannot register nil convertFunc")
}
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() == reflect.Interface {
cf.interfaceToTypeConvertMap = append(cf.interfaceToTypeConvertMap, interfaceTypeConverter{
interfaceType: t,
convertFunc: convertFunc,
})
return
}
cf.anyToTypeConvertMap[t] = convertFunc
}

func (cf *Converter) checkTypeImplInterface(t reflect.Type) AnyConvertFunc {
if t.Kind() != reflect.Ptr {
t = reflect.PointerTo(t)
}
for _, inter := range cf.interfaceToTypeConvertMap {
if t.Implements(inter.interfaceType) {
return inter.convertFunc
}
}
return nil
}

var (
implUnmarshalText = reflect.TypeOf((*localinterface.IUnmarshalText)(nil)).Elem()
implUnmarshalJson = reflect.TypeOf((*localinterface.IUnmarshalJSON)(nil)).Elem()
Expand Down
2 changes: 1 addition & 1 deletion util/gconv/internal/structcache/structcache_cached.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func (cf *Converter) GetCachedStructInfo(structType reflect.Type, priorityTag st
// else create one.

// it parses and generates a cache info for given struct type.
cachedStructInfo = NewCachedStructInfo(cf.typeConverterFuncMarkMap, cf.anyToTypeConvertMap)
cachedStructInfo = NewCachedStructInfo(cf)
var (
priorityTagArray []string
parentIndex = make([]int, 0)
Expand Down
49 changes: 24 additions & 25 deletions util/gconv/internal/structcache/structcache_cached_struct_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,7 @@ type CachedStructInfo struct {
// All sub attributes field info slice.
fieldConvertInfos []*CachedFieldInfo

// anyToTypeConvertMap for custom type converting from any to its reflect.Value.
anyToTypeConvertMap map[reflect.Type]AnyConvertFunc

// typeConverterFuncMarkMap is used to store whether field types are registered to custom conversions,
// for enhance converting performance in runtime.
typeConverterFuncMarkMap map[reflect.Type]struct{}
converter *Converter

// This map field is mainly used in the bindStructWithLoopParamsMap method
// key = field's name
Expand All @@ -36,15 +31,11 @@ type CachedStructInfo struct {
}

// NewCachedStructInfo creates and returns a new CachedStructInfo object.
func NewCachedStructInfo(
typeConverterFuncMarkMap map[reflect.Type]struct{},
anyToTypeConvertMap map[reflect.Type]AnyConvertFunc,
) *CachedStructInfo {
func NewCachedStructInfo(converter *Converter) *CachedStructInfo {
return &CachedStructInfo{
tagOrFiledNameToFieldInfoMap: make(map[string]*CachedFieldInfo),
fieldConvertInfos: make([]*CachedFieldInfo, 0),
typeConverterFuncMarkMap: typeConverterFuncMarkMap,
anyToTypeConvertMap: anyToTypeConvertMap,
converter: converter,
}
}

Expand Down Expand Up @@ -134,19 +125,27 @@ func (csi *CachedStructInfo) makeCachedFieldInfo(
}

func (csi *CachedStructInfo) genFieldConvertFunc(fieldType reflect.Type) (convertFunc AnyConvertFunc) {
if v := csi.anyToTypeConvertMap[fieldType]; v != nil {
return v
ptr := 0
for fieldType.Kind() == reflect.Ptr {
fieldType = fieldType.Elem()
ptr++
}

var fieldTypeKind = fieldType.Kind()
if fieldTypeKind == reflect.Ptr {
convertFunc = csi.genFieldConvertFunc(fieldType.Elem())
if convertFunc == nil {
return nil
}
return csi.genPtrConvertFunc(convertFunc)
convertFunc = csi.converter.anyToTypeConvertMap[fieldType]
if convertFunc == nil {
// If the registered custom implementation cannot be found,
// try to check if there is an implementation interface
convertFunc = csi.converter.checkTypeImplInterface(fieldType)
}
// if the registered type is not found and
// the corresponding interface is not implemented, return directly
if convertFunc == nil {
return nil
}
for i := 0; i < ptr; i++ {
// If it is a pointer type, it needs to be packaged
convertFunc = genPtrConvertFunc(convertFunc)
}
return nil
return convertFunc
}

func (csi *CachedStructInfo) genPriorityTagAndFieldName(
Expand All @@ -173,7 +172,7 @@ func (csi *CachedStructInfo) genPriorityTagAndFieldName(
return
}

func (csi *CachedStructInfo) genPtrConvertFunc(convertFunc AnyConvertFunc) AnyConvertFunc {
func genPtrConvertFunc(convertFunc AnyConvertFunc) AnyConvertFunc {
return func(from any, to reflect.Value) error {
if to.IsNil() {
to.Set(reflect.New(to.Type().Elem()))
Expand All @@ -186,6 +185,6 @@ func (csi *CachedStructInfo) checkTypeHasCustomConvert(fieldType reflect.Type) b
if fieldType.Kind() == reflect.Ptr {
fieldType = fieldType.Elem()
}
_, ok := csi.typeConverterFuncMarkMap[fieldType]
_, ok := csi.converter.typeConverterFuncMarkMap[fieldType]
return ok
}

0 comments on commit 5feda53

Please sign in to comment.