diff --git a/util/gconv/gconv_convert.go b/util/gconv/gconv_convert.go index d3d352c29..609d9e61e 100644 --- a/util/gconv/gconv_convert.go +++ b/util/gconv/gconv_convert.go @@ -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, diff --git a/util/gconv/gconv_converter.go b/util/gconv/gconv_converter.go index d28f6abaf..a5d7bb72f 100644 --- a/util/gconv/gconv_converter.go +++ b/util/gconv/gconv_converter.go @@ -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 } diff --git a/util/gconv/gconv_struct.go b/util/gconv/gconv_struct.go index 06fe661c0..fea556a41 100644 --- a/util/gconv/gconv_struct.go +++ b/util/gconv/gconv_struct.go @@ -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) diff --git a/util/gconv/gconv_z_unit_converter_test.go b/util/gconv/gconv_z_unit_converter_test.go index 790d23b5f..c477391d7 100644 --- a/util/gconv/gconv_z_unit_converter_test.go +++ b/util/gconv/gconv_z_unit_converter_test.go @@ -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) }) }