mirror of
https://github.com/gogf/gf.git
synced 2025-04-05 11:18:50 +08:00
Add gconv custom converter feature. (#2828)
This commit is contained in:
parent
7c1be3eb63
commit
932f8c48ef
104
util/gconv/gconv_converter.go
Normal file
104
util/gconv/gconv_converter.go
Normal file
@ -0,0 +1,104 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
var customConverters map[reflect.Type]map[reflect.Type]reflect.Value
|
||||
|
||||
func init() {
|
||||
customConverters = make(map[reflect.Type]map[reflect.Type]reflect.Value)
|
||||
}
|
||||
|
||||
// RegisterConverter to register custom converter.
|
||||
// It must be register before you use gconv. So suggest 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.
|
||||
func RegisterConverter(fn interface{}) (err error) {
|
||||
fnReflectValue := reflect.ValueOf(fn)
|
||||
fnReflectType := fnReflectValue.Type()
|
||||
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).")
|
||||
return
|
||||
}
|
||||
|
||||
inType := fnReflectType.In(0)
|
||||
outType := fnReflectType.Out(0)
|
||||
|
||||
subMap, ok := customConverters[inType]
|
||||
if !ok {
|
||||
subMap = make(map[reflect.Type]reflect.Value)
|
||||
customConverters[inType] = subMap
|
||||
}
|
||||
|
||||
if _, ok := subMap[outType]; ok {
|
||||
err = gerror.NewCode(gcode.CodeOperationFailed, "The converter has been registered.")
|
||||
return
|
||||
}
|
||||
|
||||
subMap[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()
|
||||
}
|
||||
if pointerReflectValue.Kind() != reflect.Pointer && pointerReflectValue.CanAddr() {
|
||||
pointerReflectValue = pointerReflectValue.Addr()
|
||||
}
|
||||
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 reflectValue.Kind() == reflect.Pointer {
|
||||
reflectValue = reflectValue.Elem()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
@ -143,6 +143,11 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
|
||||
pointerElemReflectValue = pointerReflectValue.Elem()
|
||||
}
|
||||
|
||||
// custom convert try first
|
||||
if ok, err := callCustomConverter(paramsReflectValue, pointerReflectValue); ok {
|
||||
return err
|
||||
}
|
||||
|
||||
// If `params` and `pointer` are the same type, the do directly assignment.
|
||||
// For performance enhancement purpose.
|
||||
if pointerElemReflectValue.IsValid() {
|
||||
@ -379,6 +384,11 @@ func bindVarToStructAttr(structReflectValue reflect.Value, attrName string, valu
|
||||
return
|
||||
}
|
||||
|
||||
// Try to call custom converter.
|
||||
if ok, err := callCustomConverter(reflect.ValueOf(value), structFieldValue); ok {
|
||||
return err
|
||||
}
|
||||
|
||||
// Common interface check.
|
||||
var ok bool
|
||||
if err, ok = bindVarToReflectValueWithInterfaceCheck(structFieldValue, value); ok {
|
||||
|
114
util/gconv/gconv_z_unit_converter_test.go
Normal file
114
util/gconv/gconv_z_unit_converter_test.go
Normal file
@ -0,0 +1,114 @@
|
||||
package gconv_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
func TestConverter(t *testing.T) {
|
||||
|
||||
type tA struct {
|
||||
Val int
|
||||
}
|
||||
|
||||
type tB struct {
|
||||
Val int32
|
||||
Val2 string
|
||||
}
|
||||
|
||||
type tAA struct {
|
||||
ValTop int
|
||||
ValTA tA
|
||||
}
|
||||
|
||||
type tBB struct {
|
||||
ValTop int32
|
||||
ValTB tB
|
||||
}
|
||||
|
||||
type tCC struct {
|
||||
ValTop string
|
||||
ValTa *tB
|
||||
}
|
||||
|
||||
type tDD struct {
|
||||
ValTop string
|
||||
ValTa tB
|
||||
}
|
||||
|
||||
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.Val2, "")
|
||||
|
||||
err = gconv.RegisterConverter(func(a tA) (b *tB, err error) {
|
||||
b = &tB{
|
||||
Val: int32(a.Val),
|
||||
Val2: "abc",
|
||||
}
|
||||
return
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gconv.Scan(a, &b)
|
||||
t.AssertNil(err)
|
||||
t.AssertNE(b, nil)
|
||||
t.Assert(b.Val, 1)
|
||||
t.Assert(b.Val2, "abc")
|
||||
|
||||
aa := &tAA{
|
||||
ValTop: 123,
|
||||
ValTA: tA{Val: 234},
|
||||
}
|
||||
|
||||
var bb *tBB
|
||||
|
||||
err = gconv.Scan(aa, &bb)
|
||||
t.AssertNil(err)
|
||||
t.AssertNE(bb, nil)
|
||||
t.Assert(bb.ValTop, 123)
|
||||
t.AssertNE(bb.ValTB.Val, 234)
|
||||
|
||||
err = gconv.RegisterConverter(func(a tAA) (b *tBB, err error) {
|
||||
b = &tBB{
|
||||
ValTop: int32(a.ValTop) + 2,
|
||||
}
|
||||
err = gconv.Scan(a.ValTA, &b.ValTB)
|
||||
return
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gconv.Scan(aa, &bb)
|
||||
t.AssertNil(err)
|
||||
t.AssertNE(bb, nil)
|
||||
t.Assert(bb.ValTop, 125)
|
||||
t.Assert(bb.ValTB.Val, 234)
|
||||
t.Assert(bb.ValTB.Val2, "abc")
|
||||
|
||||
var cc *tCC
|
||||
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.Val2, "abc")
|
||||
|
||||
var dd *tDD
|
||||
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.Val2, "abc")
|
||||
|
||||
})
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user