Skip to content

Commit

Permalink
improve converter feature for package gconv
Browse files Browse the repository at this point in the history
  • Loading branch information
gqcn committed Aug 16, 2023
1 parent 0ba5ab2 commit 26daf7c
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 49 deletions.
3 changes: 2 additions & 1 deletion util/gconv/gconv_convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import (
)

// Convert converts the variable `fromValue` to the type `toTypeName`, the type `toTypeName` is specified by string.
//
// The optional parameter `extraParams` is used for additional necessary parameter for this conversion.
// It supports common types conversion as its conversion based on type name string.
// It supports common basic types conversion as its conversion based on type name string.
func Convert(fromValue interface{}, toTypeName string, extraParams ...interface{}) interface{} {
return doConvert(doConvertInput{
FromValue: fromValue,
Expand Down
70 changes: 41 additions & 29 deletions util/gconv/gconv_converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var customConverters = make(map[converterInType]map[converterOutType]converterFu
// Note:
// 1. The parameter `fn` must be defined as pattern `func(T1) (T2, error)`.
// It will convert type `T1` to type `T2`.
// 2. The `T1`/`T2` cannot be pointer type. It automatically does the pointer converting internally.
// 2. The `T1` should not be type of pointer, but the `T2` should be type of pointer.
func RegisterConverter(fn interface{}) (err error) {
var (
fnReflectType = reflect.TypeOf(fn)
Expand All @@ -54,15 +54,15 @@ func RegisterConverter(fn interface{}) (err error) {
if inType.Kind() == reflect.Pointer {
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
"input parameter type `%s` of converter function should not be type of pointer",
"invalid input parameter type `%s`: should not be type of pointer",
inType.String(),
)
return
}
if outType.Kind() == reflect.Pointer {
if outType.Kind() != reflect.Pointer {
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
"output parameter type `%s` of converter function should not be type of pointer",
"invalid output parameter type `%s`: should be type of pointer",
outType.String(),
)
return
Expand All @@ -87,25 +87,33 @@ func RegisterConverter(fn interface{}) (err error) {

// callCustomConverter call the custom converter. It will try some possible type.
func callCustomConverter(srcReflectValue reflect.Value, dstReflectValue reflect.Value) (converted bool, err error) {
if len(customConverters) == 0 {
return false, nil
}
var (
ok bool
srcType = srcReflectValue.Type()
dstType = dstReflectValue.Type()
)
for srcType.Kind() == reflect.Pointer {
srcType = srcType.Elem()
}
for dstType.Kind() == reflect.Pointer {
dstType = dstType.Elem()
}
var (
registeredOutTypeMap map[converterOutType]converterFunc
registeredConverterFunc converterFunc
)
// firstly, it searches the map by input parameter type.
registeredOutTypeMap, ok = customConverters[srcType]
if !ok {
return false, nil
}
var dstType = dstReflectValue.Type()
if dstType.Kind() == reflect.Pointer && dstReflectValue.Elem().Kind() == reflect.Pointer {
dstType = dstReflectValue.Elem().Type()
} else if dstType.Kind() != reflect.Pointer && dstReflectValue.CanAddr() {
dstType = dstReflectValue.Addr().Type()
}
// secondly, it searches the input parameter type map
// and finds the result converter function by the output parameter type.
registeredConverterFunc, ok = registeredOutTypeMap[dstType]
if !ok {
return false, nil
Expand All @@ -115,29 +123,33 @@ func callCustomConverter(srcReflectValue reflect.Value, dstReflectValue reflect.
srcReflectValue = srcReflectValue.Elem()
}
result := registeredConverterFunc.Call([]reflect.Value{srcReflectValue})
// The `result[0]` must have a default value.
if !result[1].IsNil() {
return false, result[1].Interface().(error)
}
// The `result[0]` is a pointer.
if result[0].IsNil() {
return false, nil
}
var resultValue = result[0]
for resultValue.Type() != dstReflectValue.Type() {
if resultValue.CanAddr() {
resultValue = resultValue.Addr()
for {
if resultValue.Type() == dstReflectValue.Type() && dstReflectValue.CanSet() {
dstReflectValue.Set(resultValue)
converted = true
} else if dstReflectValue.Kind() == reflect.Pointer {
if resultValue.Type() == dstReflectValue.Elem().Type() && dstReflectValue.Elem().CanSet() {
dstReflectValue.Elem().Set(resultValue)
converted = true
}
}
if converted {
break
}
if resultValue.Kind() == reflect.Pointer {
resultValue = resultValue.Elem()
} else {
newReflectValue := reflect.New(resultValue.Type())
newReflectValue.Elem().Set(resultValue)
resultValue = newReflectValue
break
}
}
converted = true
if dstReflectValue.CanSet() {
dstReflectValue.Set(resultValue)
} else if dstReflectValue.Elem().CanSet() {
dstReflectValue.Elem().Set(resultValue.Elem())
} else {
converted = false
}
if result[1].IsNil() {
err = nil
} else {
err = result[1].Interface().(error)
}
return converted, err

return converted, nil
}
10 changes: 8 additions & 2 deletions util/gconv/gconv_struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,14 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
// For example, if `pointer` is **User, then `elem` is *User, which is a pointer to User.
if pointerElemReflectValue.Kind() == reflect.Ptr {
if !pointerElemReflectValue.IsValid() || pointerElemReflectValue.IsNil() {
e := reflect.New(pointerElemReflectValue.Type().Elem()).Elem()
pointerElemReflectValue.Set(e.Addr())
e := reflect.New(pointerElemReflectValue.Type().Elem())
pointerElemReflectValue.Set(e)
defer func() {
if err != nil {
// If it is converted failed, it reset the `pointer` to nil.
pointerReflectValue.Elem().Set(reflect.Zero(pointerReflectValue.Type().Elem()))
}
}()
}
// if v, ok := pointerElemReflectValue.Interface().(iUnmarshalValue); ok {
// return v.UnmarshalValue(params)
Expand Down
52 changes: 35 additions & 17 deletions util/gconv/gconv_z_unit_converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ func TestConverter_Struct(t *testing.T) {
})

gtest.C(t, func(t *gtest.T) {
err := gconv.RegisterConverter(func(a tA) (b tB, err error) {
b = tB{
err := gconv.RegisterConverter(func(a tA) (b *tB, err error) {
b = &tB{
Val1: int32(a.Val),
Val2: "abc",
}
Expand Down Expand Up @@ -127,8 +127,8 @@ func TestConverter_Struct(t *testing.T) {
t.Assert(bb.ValTop, 123)
t.AssertNE(bb.ValTB.Val1, 234)

err = gconv.RegisterConverter(func(a tAA) (b tBB, err error) {
b = tBB{
err = gconv.RegisterConverter(func(a tAA) (b *tBB, err error) {
b = &tBB{
ValTop: int32(a.ValTop) + 2,
}
err = gconv.Scan(a.ValTA, &b.ValTB)
Expand Down Expand Up @@ -176,7 +176,7 @@ func TestConverter_Struct(t *testing.T) {
})
}

func TestConverter_CustomBasicType(t *testing.T) {
func TestConverter_CustomBasicType_ToStruct(t *testing.T) {
type CustomString string
type CustomStruct struct {
S string
Expand All @@ -188,18 +188,36 @@ func TestConverter_CustomBasicType(t *testing.T) {
)
err := gconv.Scan(a, &b)
t.AssertNE(err, nil)
t.AssertNE(b, nil)
t.Assert(b.S, "")
t.Assert(b, nil)
})

//gtest.C(t, func(t *gtest.T) {
// err := gconv.RegisterConverter(func(a tA) (b tB, err error) {
// b = tB{
// Val1: int32(a.Val),
// Val2: "abc",
// }
// return
// })
// t.AssertNil(err)
//})
gtest.C(t, func(t *gtest.T) {
err := gconv.RegisterConverter(func(a CustomString) (b *CustomStruct, err error) {
b = &CustomStruct{
S: string(a),
}
return
})
t.AssertNil(err)
})
gtest.C(t, func(t *gtest.T) {
var (
a CustomString = "abc"
b *CustomStruct
)
err := gconv.Scan(a, &b)
t.AssertNil(err)
t.AssertNE(b, nil)
t.Assert(b.S, a)
})
gtest.C(t, func(t *gtest.T) {
var (
a CustomString = "abc"
b *CustomStruct
)
err := gconv.Scan(&a, &b)
t.AssertNil(err)
t.AssertNE(b, nil)
t.Assert(b.S, a)
})
}

0 comments on commit 26daf7c

Please sign in to comment.