Skip to content

Commit

Permalink
improve converter feature for package gconv (#2869)
Browse files Browse the repository at this point in the history
  • Loading branch information
gqcn authored Aug 17, 2023
1 parent ac3481e commit ed4d155
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 79 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
169 changes: 110 additions & 59 deletions util/gconv/gconv_converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
var (
ok bool
srcType = srcReflectValue.Type()
)
for srcType.Kind() == reflect.Pointer {
srcType = srcType.Elem()
}
if pointerReflectValue.Kind() != reflect.Pointer && pointerReflectValue.CanAddr() {
pointerReflectValue = pointerReflectValue.Addr()
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
}
12 changes: 9 additions & 3 deletions util/gconv/gconv_struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down 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
Loading

0 comments on commit ed4d155

Please sign in to comment.