mirror of
https://gitee.com/johng/gf
synced 2026-06-06 16:21:40 +08:00
improve converter feature for package gconv (#2869)
This commit is contained in:
@ -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,
|
||||
|
||||
@ -13,92 +13,143 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
var customConverters map[reflect.Type]map[reflect.Type]reflect.Value
|
||||
type (
|
||||
converterInType = reflect.Type
|
||||
converterOutType = reflect.Type
|
||||
converterFunc = reflect.Value
|
||||
)
|
||||
|
||||
func init() {
|
||||
customConverters = make(map[reflect.Type]map[reflect.Type]reflect.Value)
|
||||
}
|
||||
// customConverters for internal converter storing.
|
||||
var customConverters = make(map[converterInType]map[converterOutType]converterFunc)
|
||||
|
||||
// RegisterConverter to register custom converter.
|
||||
// It must be register before you use gconv. So suggest to do it in boot.
|
||||
// It must be registered before you use this custom converting feature.
|
||||
// It is suggested to do it in boot.
|
||||
//
|
||||
// Note:
|
||||
// 1. The fn must be func(T1)(T2,error). It will convert T1 to T2.
|
||||
// 2. The T1 and T2 must be pointer.
|
||||
// 1. The parameter `fn` must be defined as pattern `func(T1) (T2, error)`.
|
||||
// It will convert type `T1` to type `T2`.
|
||||
// 2. The `T1` should not be type of pointer, but the `T2` should be type of pointer.
|
||||
func RegisterConverter(fn interface{}) (err error) {
|
||||
fnReflectValue := reflect.ValueOf(fn)
|
||||
fnReflectType := fnReflectValue.Type()
|
||||
errType := reflect.TypeOf((*error)(nil)).Elem()
|
||||
|
||||
var (
|
||||
fnReflectType = reflect.TypeOf(fn)
|
||||
errType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
)
|
||||
if fnReflectType.Kind() != reflect.Func ||
|
||||
fnReflectType.NumIn() != 1 || fnReflectType.NumOut() != 2 ||
|
||||
!fnReflectType.Out(1).Implements(errType) {
|
||||
err = gerror.NewCode(gcode.CodeInvalidParameter, "The gconv.RegisterConverter's parameter must be a function as func(T1)(T2,error).")
|
||||
err = gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
"parameter must be type of function and defined as pattern `func(T1) (T2, error)`, but defined as `%s`",
|
||||
fnReflectType.String(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
inType := fnReflectType.In(0)
|
||||
outType := fnReflectType.Out(0)
|
||||
// The Key and Value of the converter map should not be pointer.
|
||||
var (
|
||||
inType = fnReflectType.In(0)
|
||||
outType = fnReflectType.Out(0)
|
||||
)
|
||||
if inType.Kind() == reflect.Pointer {
|
||||
err = gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
"invalid input parameter type `%s`: should not be type of pointer",
|
||||
inType.String(),
|
||||
)
|
||||
return
|
||||
}
|
||||
if outType.Kind() != reflect.Pointer {
|
||||
err = gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
"invalid output parameter type `%s`: should be type of pointer",
|
||||
outType.String(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
subMap, ok := customConverters[inType]
|
||||
registeredOutTypeMap, ok := customConverters[inType]
|
||||
if !ok {
|
||||
subMap = make(map[reflect.Type]reflect.Value)
|
||||
customConverters[inType] = subMap
|
||||
registeredOutTypeMap = make(map[converterOutType]converterFunc)
|
||||
customConverters[inType] = registeredOutTypeMap
|
||||
}
|
||||
|
||||
if _, ok := subMap[outType]; ok {
|
||||
err = gerror.NewCode(gcode.CodeOperationFailed, "The converter has been registered.")
|
||||
if _, ok = registeredOutTypeMap[outType]; ok {
|
||||
err = gerror.NewCodef(
|
||||
gcode.CodeInvalidOperation,
|
||||
"the converter parameter type `%s` to type `%s` has already been registered",
|
||||
inType.String(), outType.String(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
subMap[outType] = reflect.ValueOf(fn)
|
||||
registeredOutTypeMap[outType] = reflect.ValueOf(fn)
|
||||
return
|
||||
}
|
||||
|
||||
// callCustomConverter call the custom converter. It will try some possible type.
|
||||
func callCustomConverter(reflectValue reflect.Value, pointerReflectValue reflect.Value) (ok bool, err error) {
|
||||
if reflectValue.Kind() != reflect.Pointer && reflectValue.CanAddr() {
|
||||
reflectValue = reflectValue.Addr()
|
||||
func callCustomConverter(srcReflectValue reflect.Value, dstReflectValue reflect.Value) (converted bool, err error) {
|
||||
if len(customConverters) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
if pointerReflectValue.Kind() != reflect.Pointer && pointerReflectValue.CanAddr() {
|
||||
pointerReflectValue = pointerReflectValue.Addr()
|
||||
var (
|
||||
ok bool
|
||||
srcType = srcReflectValue.Type()
|
||||
)
|
||||
for srcType.Kind() == reflect.Pointer {
|
||||
srcType = srcType.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
|
||||
}
|
||||
// Converter function calling.
|
||||
for srcReflectValue.Type() != srcType {
|
||||
srcReflectValue = srcReflectValue.Elem()
|
||||
}
|
||||
result := registeredConverterFunc.Call([]reflect.Value{srcReflectValue})
|
||||
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 {
|
||||
if !reflectValue.IsValid() {
|
||||
break
|
||||
}
|
||||
subMap, ok := customConverters[reflectValue.Type()]
|
||||
if ok {
|
||||
pointerTmpReflectValue := pointerReflectValue
|
||||
for {
|
||||
if !pointerTmpReflectValue.IsValid() {
|
||||
break
|
||||
}
|
||||
if converter, ok := subMap[pointerTmpReflectValue.Type()]; ok {
|
||||
ret := converter.Call([]reflect.Value{reflectValue})
|
||||
if pointerTmpReflectValue.CanSet() {
|
||||
pointerTmpReflectValue.Set(ret[0])
|
||||
} else if pointerTmpReflectValue.Elem().CanSet() {
|
||||
pointerTmpReflectValue.Elem().Set(ret[0].Elem())
|
||||
}
|
||||
if ret[1].IsNil() {
|
||||
err = nil
|
||||
} else {
|
||||
err = ret[1].Interface().(error)
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
if pointerTmpReflectValue.Kind() == reflect.Pointer {
|
||||
pointerTmpReflectValue = pointerTmpReflectValue.Elem()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
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 reflectValue.Kind() == reflect.Pointer {
|
||||
reflectValue = reflectValue.Elem()
|
||||
if converted {
|
||||
break
|
||||
}
|
||||
if resultValue.Kind() == reflect.Pointer {
|
||||
resultValue = resultValue.Elem()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
|
||||
return converted, nil
|
||||
}
|
||||
|
||||
@ -144,7 +144,7 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
|
||||
}
|
||||
|
||||
// custom convert try first
|
||||
if ok, err := callCustomConverter(paramsReflectValue, pointerReflectValue); ok {
|
||||
if ok, err = callCustomConverter(paramsReflectValue, pointerReflectValue); ok {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gconv_test
|
||||
|
||||
import (
|
||||
@ -7,14 +13,13 @@ import (
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
func TestConverter(t *testing.T) {
|
||||
|
||||
func TestConverter_Struct(t *testing.T) {
|
||||
type tA struct {
|
||||
Val int
|
||||
}
|
||||
|
||||
type tB struct {
|
||||
Val int32
|
||||
Val1 int32
|
||||
Val2 string
|
||||
}
|
||||
|
||||
@ -46,36 +51,81 @@ func TestConverter(t *testing.T) {
|
||||
err := gconv.Scan(a, &b)
|
||||
t.AssertNil(err)
|
||||
t.AssertNE(b, nil)
|
||||
t.Assert(b.Val, 1)
|
||||
t.Assert(b.Val1, 0)
|
||||
t.Assert(b.Val2, "")
|
||||
})
|
||||
|
||||
err = gconv.RegisterConverter(func(a tA) (b *tB, err error) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := gconv.RegisterConverter(func(a tA) (b *tB, err error) {
|
||||
b = &tB{
|
||||
Val: int32(a.Val),
|
||||
Val1: int32(a.Val),
|
||||
Val2: "abc",
|
||||
}
|
||||
return
|
||||
})
|
||||
t.AssertNil(err)
|
||||
})
|
||||
|
||||
err = gconv.Scan(a, &b)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
a := &tA{
|
||||
Val: 1,
|
||||
}
|
||||
var b *tB
|
||||
err := gconv.Scan(a, &b)
|
||||
t.AssertNil(err)
|
||||
t.AssertNE(b, nil)
|
||||
t.Assert(b.Val, 1)
|
||||
t.Assert(b.Val1, 1)
|
||||
t.Assert(b.Val2, "abc")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
a := &tA{
|
||||
Val: 1,
|
||||
}
|
||||
var b *tB
|
||||
err := gconv.Scan(a, &b)
|
||||
t.AssertNil(err)
|
||||
t.AssertNE(b, nil)
|
||||
t.Assert(b.Val1, 1)
|
||||
t.Assert(b.Val2, "abc")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
a := &tA{
|
||||
Val: 1,
|
||||
}
|
||||
var b *tB
|
||||
err := gconv.Scan(a, &b)
|
||||
t.AssertNil(err)
|
||||
t.AssertNE(b, nil)
|
||||
t.Assert(b.Val1, 1)
|
||||
t.Assert(b.Val2, "abc")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
a := &tA{
|
||||
Val: 1,
|
||||
}
|
||||
var b *tB
|
||||
err := gconv.Scan(a, &b)
|
||||
t.AssertNil(err)
|
||||
t.AssertNE(b, nil)
|
||||
t.Assert(b.Val1, 1)
|
||||
t.Assert(b.Val2, "abc")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
aa := &tAA{
|
||||
ValTop: 123,
|
||||
ValTA: tA{Val: 234},
|
||||
}
|
||||
|
||||
var bb *tBB
|
||||
|
||||
err = gconv.Scan(aa, &bb)
|
||||
err := gconv.Scan(aa, &bb)
|
||||
t.AssertNil(err)
|
||||
t.AssertNE(bb, nil)
|
||||
t.Assert(bb.ValTop, 123)
|
||||
t.AssertNE(bb.ValTB.Val, 234)
|
||||
t.AssertNE(bb.ValTB.Val1, 234)
|
||||
|
||||
err = gconv.RegisterConverter(func(a tAA) (b *tBB, err error) {
|
||||
b = &tBB{
|
||||
@ -90,25 +140,84 @@ func TestConverter(t *testing.T) {
|
||||
t.AssertNil(err)
|
||||
t.AssertNE(bb, nil)
|
||||
t.Assert(bb.ValTop, 125)
|
||||
t.Assert(bb.ValTB.Val, 234)
|
||||
t.Assert(bb.ValTB.Val1, 234)
|
||||
t.Assert(bb.ValTB.Val2, "abc")
|
||||
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
aa := &tAA{
|
||||
ValTop: 123,
|
||||
ValTA: tA{Val: 234},
|
||||
}
|
||||
var cc *tCC
|
||||
err = gconv.Scan(aa, &cc)
|
||||
err := gconv.Scan(aa, &cc)
|
||||
t.AssertNil(err)
|
||||
t.AssertNE(cc, nil)
|
||||
t.Assert(cc.ValTop, "123")
|
||||
t.AssertNE(cc.ValTa, nil)
|
||||
t.Assert(cc.ValTa.Val, 234)
|
||||
t.Assert(cc.ValTa.Val1, 234)
|
||||
t.Assert(cc.ValTa.Val2, "abc")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
aa := &tAA{
|
||||
ValTop: 123,
|
||||
ValTA: tA{Val: 234},
|
||||
}
|
||||
|
||||
var dd *tDD
|
||||
err = gconv.Scan(aa, &dd)
|
||||
err := gconv.Scan(aa, &dd)
|
||||
t.AssertNil(err)
|
||||
t.AssertNE(dd, nil)
|
||||
t.Assert(dd.ValTop, "123")
|
||||
t.Assert(dd.ValTa.Val, 234)
|
||||
t.Assert(dd.ValTa.Val1, 234)
|
||||
t.Assert(dd.ValTa.Val2, "abc")
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestConverter_CustomBasicType_ToStruct(t *testing.T) {
|
||||
type CustomString string
|
||||
type CustomStruct struct {
|
||||
S string
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
a CustomString = "abc"
|
||||
b *CustomStruct
|
||||
)
|
||||
err := gconv.Scan(a, &b)
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(b, nil)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user