This commit is contained in:
John Guo
2025-03-02 11:15:22 +08:00
parent 97c40a5879
commit 7f030248ac
12 changed files with 442 additions and 240 deletions

View File

@ -14,6 +14,8 @@ import (
"github.com/gogf/gf/v2/util/gconv/internal/structcache"
)
type AnyConvertFunc = structcache.AnyConvertFunc
var (
// Empty strings.
emptyStringMap = map[string]struct{}{
@ -29,17 +31,7 @@ var (
// Note that only pointer can implement interface IUnmarshalValue.
type IUnmarshalValue = localinterface.IUnmarshalValue
func init() {
// register common converters for internal usage.
structcache.RegisterCommonConverter(structcache.CommonConverter{
Int64: Int64,
Uint64: Uint64,
String: String,
Float32: Float32,
Float64: Float64,
Time: Time,
GTime: GTime,
Bytes: Bytes,
Bool: Bool,
})
}
var (
// defaultConvertConfig is the default configuration for type converting.
defaultConvertConfig = NewConvertConfig()
)

View File

@ -21,12 +21,15 @@ import (
// The optional parameter `extraParams` is used for additional necessary parameter for this conversion.
// 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,
ToTypeName: toTypeName,
ReferValue: nil,
Extra: extraParams,
})
return doConvert(
defaultConvertConfig,
doConvertInput{
FromValue: fromValue,
ToTypeName: toTypeName,
ReferValue: nil,
Extra: extraParams,
},
)
}
// ConvertWithRefer converts the variable `fromValue` to the type referred by value `referValue`.
@ -40,12 +43,15 @@ func ConvertWithRefer(fromValue interface{}, referValue interface{}, extraParams
} else {
referValueRf = reflect.ValueOf(referValue)
}
return doConvert(doConvertInput{
FromValue: fromValue,
ToTypeName: referValueRf.Type().String(),
ReferValue: referValue,
Extra: extraParams,
})
return doConvert(
defaultConvertConfig,
doConvertInput{
FromValue: fromValue,
ToTypeName: referValueRf.Type().String(),
ReferValue: referValue,
Extra: extraParams,
},
)
}
type doConvertInput struct {
@ -53,13 +59,14 @@ type doConvertInput struct {
ToTypeName string // Target value type name in string.
ReferValue interface{} // Referred value, a value in type `ToTypeName`. Note that its type might be reflect.Value.
Extra []interface{} // Extra values for implementing the converting.
// Marks that the value is already converted and set to `ReferValue`. Caller can ignore the returned result.
// It is an attribute for internal usage purpose.
alreadySetToReferValue bool
}
// doConvert does commonly use types converting.
func doConvert(in doConvertInput) (convertedValue interface{}) {
func doConvert(cf *ConvertConfig, in doConvertInput) (convertedValue interface{}) {
switch in.ToTypeName {
case "int":
return Int(in.FromValue)
@ -296,14 +303,14 @@ func doConvert(in doConvertInput) (convertedValue interface{}) {
}
// custom converter.
if dstReflectValue, ok, _ := callCustomConverterWithRefer(fromReflectValue, referReflectValue); ok {
if dstReflectValue, ok, _ := cf.callCustomConverterWithRefer(fromReflectValue, referReflectValue); ok {
return dstReflectValue.Interface()
}
defer func() {
if recover() != nil {
in.alreadySetToReferValue = false
if err := bindVarToReflectValue(referReflectValue, in.FromValue, nil); err == nil {
if err := bindVarToReflectValue(cf, referReflectValue, in.FromValue, nil); err == nil {
in.alreadySetToReferValue = true
convertedValue = referReflectValue.Interface()
}
@ -326,7 +333,7 @@ func doConvert(in doConvertInput) (convertedValue interface{}) {
default:
in.ToTypeName = originType.Kind().String()
in.ReferValue = nil
refElementValue := reflect.ValueOf(doConvert(in))
refElementValue := reflect.ValueOf(doConvert(cf, in))
originTypeValue := reflect.New(refElementValue.Type()).Elem()
originTypeValue.Set(refElementValue)
in.alreadySetToReferValue = true
@ -335,7 +342,7 @@ func doConvert(in doConvertInput) (convertedValue interface{}) {
case reflect.Map:
var targetValue = reflect.New(referReflectValue.Type()).Elem()
if err := doMapToMap(in.FromValue, targetValue); err == nil {
if err := doMapToMap(cf, in.FromValue, targetValue); err == nil {
in.alreadySetToReferValue = true
}
return targetValue.Interface()
@ -343,15 +350,15 @@ func doConvert(in doConvertInput) (convertedValue interface{}) {
in.ToTypeName = referReflectValue.Kind().String()
in.ReferValue = nil
in.alreadySetToReferValue = true
convertedValue = reflect.ValueOf(doConvert(in)).Convert(referReflectValue.Type()).Interface()
convertedValue = reflect.ValueOf(doConvert(cf, in)).Convert(referReflectValue.Type()).Interface()
return convertedValue
}
return in.FromValue
}
}
func doConvertWithReflectValueSet(reflectValue reflect.Value, in doConvertInput) {
convertedValue := doConvert(in)
func doConvertWithReflectValueSet(cf *ConvertConfig, reflectValue reflect.Value, in doConvertInput) {
convertedValue := doConvert(cf, in)
if !in.alreadySetToReferValue {
reflectValue.Set(reflect.ValueOf(convertedValue))
}

View File

@ -0,0 +1,99 @@
// 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"
"time"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv/internal/structcache"
)
// CommonTypeConverter holds some converting functions of common types for internal usage.
type CommonTypeConverter = structcache.CommonTypeConverter
type (
converterInType = reflect.Type
converterOutType = reflect.Type
converterFunc = reflect.Value
)
// ConvertConfig is the configuration for type converting.
type ConvertConfig struct {
internalConvertConfig *structcache.ConvertConfig
// customConverters for internal converter storing.
customConverters map[converterInType]map[converterOutType]converterFunc
}
var (
intType = reflect.TypeOf(0)
int8Type = reflect.TypeOf(int8(0))
int16Type = reflect.TypeOf(int16(0))
int32Type = reflect.TypeOf(int32(0))
int64Type = reflect.TypeOf(int64(0))
uintType = reflect.TypeOf(uint(0))
uint8Type = reflect.TypeOf(uint8(0))
uint16Type = reflect.TypeOf(uint16(0))
uint32Type = reflect.TypeOf(uint32(0))
uint64Type = reflect.TypeOf(uint64(0))
float32Type = reflect.TypeOf(float32(0))
float64Type = reflect.TypeOf(float64(0))
stringType = reflect.TypeOf("")
bytesType = reflect.TypeOf([]byte{})
boolType = reflect.TypeOf(false)
timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
gtimeType = reflect.TypeOf((*gtime.Time)(nil)).Elem()
)
// NewConvertConfig creates and returns configuration management object for type converting.
func NewConvertConfig() *ConvertConfig {
cf := &ConvertConfig{
internalConvertConfig: structcache.NewConvertConfig(),
customConverters: make(map[converterInType]map[converterOutType]converterFunc),
}
cf.registerBuiltInConverter()
return cf
}
func (cf *ConvertConfig) registerBuiltInConverter() {
cf.registerAnyConvertFuncForTypes(
builtInAnyConvertFuncForInt64, intType, int8Type, int16Type, int32Type, int64Type,
)
cf.registerAnyConvertFuncForTypes(
builtInAnyConvertFuncForUint64, uintType, uint8Type, uint16Type, uint32Type, uint64Type,
)
cf.registerAnyConvertFuncForTypes(
builtInAnyConvertFuncForString, stringType,
)
cf.registerAnyConvertFuncForTypes(
builtInAnyConvertFuncForFloat64, float32Type, float64Type,
)
cf.registerAnyConvertFuncForTypes(
builtInAnyConvertFuncForBool, boolType,
)
cf.registerAnyConvertFuncForTypes(
builtInAnyConvertFuncForBytes, bytesType,
)
cf.registerAnyConvertFuncForTypes(
builtInAnyConvertFuncForTime, timeType,
)
cf.registerAnyConvertFuncForTypes(
builtInAnyConvertFuncForGTime, gtimeType,
)
}
func (cf *ConvertConfig) registerAnyConvertFuncForTypes(convertFunc AnyConvertFunc, types ...reflect.Type) {
for _, t := range types {
cf.internalConvertConfig.RegisterAnyConvertFunc(t, convertFunc)
}
}

View File

@ -11,19 +11,14 @@ import (
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/util/gconv/internal/structcache"
)
type (
converterInType = reflect.Type
converterOutType = reflect.Type
converterFunc = reflect.Value
)
// RegisterConverter registers custom converter.
func RegisterConverter(fn any) (err error) {
return defaultConvertConfig.RegisterConverter(fn)
}
// customConverters for internal converter storing.
var customConverters = make(map[converterInType]map[converterOutType]converterFunc)
// RegisterConverter to register custom converter.
// RegisterConverter registers custom converter.
// It must be registered before you use this custom converting feature.
// It is suggested to do it in boot procedure of the process.
//
@ -31,7 +26,7 @@ var customConverters = make(map[converterInType]map[converterOutType]converterFu
// 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) {
func (cf *ConvertConfig) RegisterConverter(fn any) (err error) {
var (
fnReflectType = reflect.TypeOf(fn)
errType = reflect.TypeOf((*error)(nil)).Elem()
@ -41,7 +36,8 @@ func RegisterConverter(fn interface{}) (err error) {
!fnReflectType.Out(1).Implements(errType) {
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
"parameter must be type of converter function and defined as pattern `func(T1) (T2, error)`, but defined as `%s`",
"parameter must be type of converter function and defined as pattern `func(T1) (T2, error)`, "+
"but defined as `%s`",
fnReflectType.String(),
)
return
@ -69,10 +65,10 @@ func RegisterConverter(fn interface{}) (err error) {
return
}
registeredOutTypeMap, ok := customConverters[inType]
registeredOutTypeMap, ok := cf.customConverters[inType]
if !ok {
registeredOutTypeMap = make(map[converterOutType]converterFunc)
customConverters[inType] = registeredOutTypeMap
cf.customConverters[inType] = registeredOutTypeMap
}
if _, ok = registeredOutTypeMap[outType]; ok {
err = gerror.NewCodef(
@ -83,14 +79,14 @@ func RegisterConverter(fn interface{}) (err error) {
return
}
registeredOutTypeMap[outType] = reflect.ValueOf(fn)
structcache.RegisterCustomConvertType(outType)
cf.internalConvertConfig.RegisterCustomConvertType(outType)
return
}
func getRegisteredConverterFuncAndSrcType(
func (cf *ConvertConfig) getRegisteredConverterFuncAndSrcType(
srcReflectValue, dstReflectValueForRefer reflect.Value,
) (f converterFunc, srcType reflect.Type, ok bool) {
if len(customConverters) == 0 {
if len(cf.customConverters) == 0 {
return reflect.Value{}, nil, false
}
srcType = srcReflectValue.Type()
@ -99,7 +95,7 @@ func getRegisteredConverterFuncAndSrcType(
}
var registeredOutTypeMap map[converterOutType]converterFunc
// firstly, it searches the map by input parameter type.
registeredOutTypeMap, ok = customConverters[srcType]
registeredOutTypeMap, ok = cf.customConverters[srcType]
if !ok {
return reflect.Value{}, nil, false
}
@ -123,28 +119,28 @@ func getRegisteredConverterFuncAndSrcType(
return
}
func callCustomConverterWithRefer(
func (cf *ConvertConfig) callCustomConverterWithRefer(
srcReflectValue, referReflectValue reflect.Value,
) (dstReflectValue reflect.Value, converted bool, err error) {
registeredConverterFunc, srcType, ok := getRegisteredConverterFuncAndSrcType(srcReflectValue, referReflectValue)
registeredConverterFunc, srcType, ok := cf.getRegisteredConverterFuncAndSrcType(srcReflectValue, referReflectValue)
if !ok {
return reflect.Value{}, false, nil
}
dstReflectValue = reflect.New(referReflectValue.Type()).Elem()
converted, err = doCallCustomConverter(srcReflectValue, dstReflectValue, registeredConverterFunc, srcType)
converted, err = cf.doCallCustomConverter(srcReflectValue, dstReflectValue, registeredConverterFunc, srcType)
return
}
// callCustomConverter call the custom converter. It will try some possible type.
func callCustomConverter(srcReflectValue, dstReflectValue reflect.Value) (converted bool, err error) {
registeredConverterFunc, srcType, ok := getRegisteredConverterFuncAndSrcType(srcReflectValue, dstReflectValue)
func (cf *ConvertConfig) callCustomConverter(srcReflectValue, dstReflectValue reflect.Value) (converted bool, err error) {
registeredConverterFunc, srcType, ok := cf.getRegisteredConverterFuncAndSrcType(srcReflectValue, dstReflectValue)
if !ok {
return false, nil
}
return doCallCustomConverter(srcReflectValue, dstReflectValue, registeredConverterFunc, srcType)
return cf.doCallCustomConverter(srcReflectValue, dstReflectValue, registeredConverterFunc, srcType)
}
func doCallCustomConverter(
func (cf *ConvertConfig) doCallCustomConverter(
srcReflectValue reflect.Value,
dstReflectValue reflect.Value,
registeredConverterFunc converterFunc,

View File

@ -0,0 +1,82 @@
// 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"
"time"
"github.com/gogf/gf/v2/os/gtime"
)
func builtInAnyConvertFuncForInt64(from any, to reflect.Value) error {
v, err := doInt64(from)
if err != nil {
return err
}
to.SetInt(v)
return nil
}
func builtInAnyConvertFuncForUint64(from any, to reflect.Value) error {
v, err := doUint64(from)
if err != nil {
return err
}
to.SetUint(v)
return nil
}
func builtInAnyConvertFuncForString(from any, to reflect.Value) error {
v, err := doString(from)
if err != nil {
return err
}
to.SetString(v)
return nil
}
func builtInAnyConvertFuncForFloat64(from any, to reflect.Value) error {
v, err := doFloat64(from)
if err != nil {
return err
}
to.SetFloat(v)
return nil
}
func builtInAnyConvertFuncForBool(from any, to reflect.Value) error {
v, err := doBool(from)
if err != nil {
return err
}
to.SetBool(v)
return nil
}
func builtInAnyConvertFuncForBytes(from any, to reflect.Value) error {
v, err := doBytes(from)
if err != nil {
return err
}
to.SetBytes(v)
return nil
}
func builtInAnyConvertFuncForTime(from any, to reflect.Value) error {
*to.Addr().Interface().(*time.Time) = Time(from)
return nil
}
func builtInAnyConvertFuncForGTime(from any, to reflect.Value) error {
v := GTime(from)
if v == nil {
v = gtime.New()
}
*to.Addr().Interface().(*gtime.Time) = *v
return nil
}

View File

@ -16,7 +16,7 @@ import (
// MapToMap converts any map type variable `params` to another map type variable `pointer`
// using reflect.
// See doMapToMap.
func MapToMap(params interface{}, pointer interface{}, mapping ...map[string]string) error {
func MapToMap(params any, pointer any, mapping ...map[string]string) error {
return Scan(params, pointer, mapping...)
}
@ -30,7 +30,7 @@ func MapToMap(params interface{}, pointer interface{}, mapping ...map[string]str
//
// The optional parameter `mapping` is used for struct attribute to map key mapping, which makes
// sense only if the items of original map `params` is type struct.
func doMapToMap(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
func doMapToMap(cf *ConvertConfig, params any, pointer any, mapping ...map[string]string) (err error) {
var (
paramsRv reflect.Value
paramsKind reflect.Kind
@ -50,7 +50,7 @@ func doMapToMap(params interface{}, pointer interface{}, mapping ...map[string]s
paramsKind = paramsRv.Kind()
}
if paramsKind != reflect.Map {
return doMapToMap(Map(params), pointer, mapping...)
return doMapToMap(cf, Map(params), pointer, mapping...)
}
// Empty params map, no need continue.
if paramsRv.Len() == 0 {
@ -107,22 +107,26 @@ func doMapToMap(params interface{}, pointer interface{}, mapping ...map[string]s
default:
mapValue.Set(
reflect.ValueOf(
doConvert(doConvertInput{
FromValue: paramsRv.MapIndex(key).Interface(),
ToTypeName: pointerValueType.String(),
ReferValue: mapValue,
Extra: nil,
}),
doConvert(
cf, doConvertInput{
FromValue: paramsRv.MapIndex(key).Interface(),
ToTypeName: pointerValueType.String(),
ReferValue: mapValue,
Extra: nil,
}),
),
)
}
var mapKey = reflect.ValueOf(
doConvert(doConvertInput{
FromValue: key.Interface(),
ToTypeName: pointerKeyType.Name(),
ReferValue: reflect.New(pointerKeyType).Elem().Interface(),
Extra: nil,
}),
doConvert(
cf,
doConvertInput{
FromValue: key.Interface(),
ToTypeName: pointerKeyType.Name(),
ReferValue: reflect.New(pointerKeyType).Elem().Interface(),
Extra: nil,
},
),
)
dataMap.SetMapIndex(mapKey, mapValue)
}

View File

@ -26,6 +26,10 @@ import (
// The `paramKeyToAttrMap` parameter is used for mapping between attribute names and parameter keys.
// TODO: change `paramKeyToAttrMap` to `ScanOption` to be more scalable; add `DeepCopy` option for `ScanOption`.
func Scan(srcValue any, dstPointer any, paramKeyToAttrMap ...map[string]string) (err error) {
return ScanWithConfig(defaultConvertConfig, srcValue, dstPointer, paramKeyToAttrMap...)
}
func ScanWithConfig(cf *ConvertConfig, srcValue any, dstPointer any, paramKeyToAttrMap ...map[string]string) (err error) {
// Check if srcValue is nil, in which case no conversion is needed
if srcValue == nil {
return nil
@ -142,7 +146,7 @@ func Scan(srcValue any, dstPointer any, paramKeyToAttrMap ...map[string]string)
}
// Special handling for struct or map slice elements
if dstElemKind == reflect.Struct || dstElemKind == reflect.Map {
return doScanForComplicatedTypes(srcValue, dstPointer, dstPointerReflectType, paramKeyToAttrMap...)
return doScanForComplicatedTypes(cf, srcValue, dstPointer, dstPointerReflectType, paramKeyToAttrMap...)
}
// Handle basic type slice conversions
var srcValueReflectValueKind = srcValueReflectValue.Kind()
@ -173,11 +177,11 @@ func Scan(srcValue any, dstPointer any, paramKeyToAttrMap ...map[string]string)
dstPointerReflectValueElem.Set(newSlice)
return nil
}
return doScanForComplicatedTypes(srcValue, dstPointer, dstPointerReflectType, paramKeyToAttrMap...)
return doScanForComplicatedTypes(cf, srcValue, dstPointer, dstPointerReflectType, paramKeyToAttrMap...)
default:
// Handle complex types (structs, maps, etc.)
return doScanForComplicatedTypes(srcValue, dstPointer, dstPointerReflectType, paramKeyToAttrMap...)
return doScanForComplicatedTypes(cf, srcValue, dstPointer, dstPointerReflectType, paramKeyToAttrMap...)
}
}
@ -193,6 +197,7 @@ func Scan(srcValue any, dstPointer any, paramKeyToAttrMap ...map[string]string)
// - dstPointerReflectType: The reflection type of the destination pointer
// - paramKeyToAttrMap: Optional mapping between parameter keys and struct attribute names
func doScanForComplicatedTypes(
cf *ConvertConfig,
srcValue, dstPointer any,
dstPointerReflectType reflect.Type,
paramKeyToAttrMap ...map[string]string,
@ -220,7 +225,7 @@ func doScanForComplicatedTypes(
switch dstPointerReflectTypeElemKind {
case reflect.Map:
// Convert map to map
return doMapToMap(srcValue, dstPointer, paramKeyToAttrMap...)
return doMapToMap(cf, srcValue, dstPointer, paramKeyToAttrMap...)
case reflect.Array, reflect.Slice:
var (

View File

@ -32,20 +32,34 @@ import (
// It will automatically convert the first letter of the key to uppercase
// in mapping procedure to do the matching.
// It ignores the map key, if it does not match.
func Struct(params interface{}, pointer interface{}, paramKeyToAttrMap ...map[string]string) (err error) {
func Struct(params any, pointer any, paramKeyToAttrMap ...map[string]string) (err error) {
return Scan(params, pointer, paramKeyToAttrMap...)
}
// StructTag acts as Struct but also with support for priority tag feature, which retrieves the
// specified priorityTagAndFieldName for `params` key-value items to struct attribute names mapping.
// The parameter `priorityTag` supports multiple priorityTagAndFieldName that can be joined with char ','.
func StructTag(params interface{}, pointer interface{}, priorityTag string) (err error) {
func StructTag(params any, pointer any, priorityTag string) (err error) {
return doStruct(params, pointer, nil, priorityTag)
}
// doStruct is the core internal converting function for any data to struct.
func doStruct(
params interface{}, pointer interface{}, paramKeyToAttrMap map[string]string, priorityTag string,
params any,
pointer any,
paramKeyToAttrMap map[string]string,
priorityTag string,
) (err error) {
return doStructWithConfig(defaultConvertConfig, params, pointer, paramKeyToAttrMap, priorityTag)
}
// doStruct is the core internal converting function for any data to struct.
func doStructWithConfig(
cf *ConvertConfig,
params any,
pointer any,
paramKeyToAttrMap map[string]string,
priorityTag string,
) (err error) {
if params == nil {
// If `params` is nil, no conversion.
@ -77,7 +91,7 @@ func doStruct(
var (
paramsReflectValue reflect.Value
paramsInterface interface{} // DO NOT use `params` directly as it might be type `reflect.Value`
paramsInterface any // DO NOT use `params` directly as it might be type `reflect.Value`
pointerReflectValue reflect.Value
pointerReflectKind reflect.Kind
pointerElemReflectValue reflect.Value // The reflection value to struct element.
@ -118,7 +132,7 @@ func doStruct(
}
// custom convert.
if ok, err = callCustomConverter(paramsReflectValue, pointerReflectValue); ok {
if ok, err = cf.callCustomConverter(paramsReflectValue, pointerReflectValue); ok {
return err
}
@ -150,15 +164,15 @@ func doStruct(
// Retrieve its element, may be struct at last.
pointerElemReflectValue = pointerElemReflectValue.Elem()
}
paramsMap, ok := paramsInterface.(map[string]interface{})
paramsMap, ok := paramsInterface.(map[string]any)
if !ok {
// paramsMap is the map[string]interface{} type variable for params.
// paramsMap is the map[string]any type variable for params.
// DO NOT use MapDeep here.
paramsMap = doMapConvert(paramsInterface, recursiveTypeAuto, true)
if paramsMap == nil {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`convert params from "%#v" to "map[string]interface{}" failed`,
`convert params from "%#v" to "map[string]any" failed`,
params,
)
}
@ -168,7 +182,7 @@ func doStruct(
return nil
}
// Get struct info from cache or parse struct and cache the struct info.
cachedStructInfo := structcache.GetCachedStructInfo(
cachedStructInfo := cf.internalConvertConfig.GetCachedStructInfo(
pointerElemReflectValue.Type(), priorityTag,
)
// Nothing to be converted.
@ -184,7 +198,7 @@ func doStruct(
// Indicates that those values have been used and cannot be reused.
usedParamsKeyOrTagNameMap = structcache.GetUsedParamsKeyOrTagNameMapFromPool()
cachedFieldInfo *structcache.CachedFieldInfo
paramsValue interface{}
paramsValue any
)
defer structcache.PutUsedParamsKeyOrTagNameMapToPool(usedParamsKeyOrTagNameMap)
@ -199,16 +213,17 @@ func doStruct(
if cachedFieldInfo != nil {
fieldValue := cachedFieldInfo.GetFieldReflectValueFrom(pointerElemReflectValue)
if err = bindVarToStructField(
cf,
cachedFieldInfo,
fieldValue,
paramsValue,
cachedFieldInfo,
paramKeyToAttrMap,
); err != nil {
return err
}
if len(cachedFieldInfo.OtherSameNameField) > 0 {
if err = setOtherSameNameField(
cachedFieldInfo, paramsValue, pointerReflectValue, paramKeyToAttrMap,
cf, cachedFieldInfo, paramsValue, pointerReflectValue, paramKeyToAttrMap,
); err != nil {
return err
}
@ -221,11 +236,12 @@ func doStruct(
return nil
}
return bindStructWithLoopFieldInfos(
paramsMap, pointerElemReflectValue, paramKeyToAttrMap, usedParamsKeyOrTagNameMap, cachedStructInfo,
cf, paramsMap, pointerElemReflectValue, paramKeyToAttrMap, usedParamsKeyOrTagNameMap, cachedStructInfo,
)
}
func setOtherSameNameField(
cf *ConvertConfig,
cachedFieldInfo *structcache.CachedFieldInfo,
srcValue any,
structValue reflect.Value,
@ -234,7 +250,7 @@ func setOtherSameNameField(
// loop the same field name of all sub attributes.
for _, otherFieldInfo := range cachedFieldInfo.OtherSameNameField {
fieldValue := cachedFieldInfo.GetOtherFieldReflectValueFrom(structValue, otherFieldInfo.FieldIndexes)
if err = bindVarToStructField(fieldValue, srcValue, otherFieldInfo, paramKeyToAttrMap); err != nil {
if err = bindVarToStructField(cf, otherFieldInfo, fieldValue, srcValue, paramKeyToAttrMap); err != nil {
return err
}
}
@ -242,6 +258,7 @@ func setOtherSameNameField(
}
func bindStructWithLoopFieldInfos(
cf *ConvertConfig,
paramsMap map[string]any,
structValue reflect.Value,
paramKeyToAttrMap map[string]string,
@ -257,21 +274,21 @@ func bindStructWithLoopFieldInfos(
matched bool
ok bool
)
for _, cachedFieldInfo = range cachedStructInfo.FieldConvertInfos {
for _, cachedFieldInfo = range cachedStructInfo.GetFieldConvertInfos() {
for _, fieldTag := range cachedFieldInfo.PriorityTagAndFieldName {
if paramValue, ok = paramsMap[fieldTag]; !ok {
continue
}
fieldValue = cachedFieldInfo.GetFieldReflectValueFrom(structValue)
if err = bindVarToStructField(
fieldValue, paramValue, cachedFieldInfo, paramKeyToAttrMap,
cf, cachedFieldInfo, fieldValue, paramValue, paramKeyToAttrMap,
); err != nil {
return err
}
// handle same field name in nested struct.
if len(cachedFieldInfo.OtherSameNameField) > 0 {
if err = setOtherSameNameField(
cachedFieldInfo, paramValue, structValue, paramKeyToAttrMap,
cf, cachedFieldInfo, paramValue, structValue, paramKeyToAttrMap,
); err != nil {
return err
}
@ -297,14 +314,14 @@ func bindStructWithLoopFieldInfos(
fieldValue = cachedFieldInfo.GetFieldReflectValueFrom(structValue)
if paramValue != nil {
if err = bindVarToStructField(
fieldValue, paramValue, cachedFieldInfo, paramKeyToAttrMap,
cf, cachedFieldInfo, fieldValue, paramValue, paramKeyToAttrMap,
); err != nil {
return err
}
// handle same field name in nested struct.
if len(cachedFieldInfo.OtherSameNameField) > 0 {
if err = setOtherSameNameField(
cachedFieldInfo, paramValue, structValue, paramKeyToAttrMap,
cf, cachedFieldInfo, paramValue, structValue, paramKeyToAttrMap,
); err != nil {
return err
}
@ -338,9 +355,10 @@ func fuzzyMatchingFieldName(
// bindVarToStructField sets value to struct object attribute by name.
// each value to attribute converting comes into in this function.
func bindVarToStructField(
fieldValue reflect.Value,
srcValue interface{},
cf *ConvertConfig,
cachedFieldInfo *structcache.CachedFieldInfo,
fieldValue reflect.Value,
srcValue any,
paramKeyToAttrMap map[string]string,
) (err error) {
if !fieldValue.IsValid() {
@ -352,7 +370,7 @@ func bindVarToStructField(
}
defer func() {
if exception := recover(); exception != nil {
if err = bindVarToReflectValue(fieldValue, srcValue, paramKeyToAttrMap); err != nil {
if err = bindVarToReflectValue(cf, fieldValue, srcValue, paramKeyToAttrMap); err != nil {
err = gerror.Wrapf(err, `error binding srcValue to attribute "%s"`, cachedFieldInfo.FieldName())
}
}
@ -372,7 +390,7 @@ func bindVarToStructField(
if customConverterInput, ok = srcValue.(reflect.Value); !ok {
customConverterInput = reflect.ValueOf(srcValue)
}
if ok, err = callCustomConverter(customConverterInput, fieldValue); ok || err != nil {
if ok, err = cf.callCustomConverter(customConverterInput, fieldValue); ok || err != nil {
return
}
}
@ -383,20 +401,21 @@ func bindVarToStructField(
}
// Common types use fast assignment logic
if cachedFieldInfo.ConvertFunc != nil {
cachedFieldInfo.ConvertFunc(srcValue, fieldValue)
return nil
return cachedFieldInfo.ConvertFunc(srcValue, fieldValue)
}
doConvertWithReflectValueSet(fieldValue, doConvertInput{
FromValue: srcValue,
ToTypeName: cachedFieldInfo.StructField.Type.String(),
ReferValue: fieldValue,
})
doConvertWithReflectValueSet(
cf, fieldValue, doConvertInput{
FromValue: srcValue,
ToTypeName: cachedFieldInfo.StructField.Type.String(),
ReferValue: fieldValue,
},
)
return nil
}
// bindVarToReflectValueWithInterfaceCheck does bind using common interfaces checks.
func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value interface{}) (bool, error) {
var pointer interface{}
func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value any) (bool, error) {
var pointer any
if reflectValue.Kind() != reflect.Ptr && reflectValue.CanAddr() {
reflectValueAddr := reflectValue.Addr()
if reflectValueAddr.IsNil() || !reflectValueAddr.IsValid() {
@ -460,7 +479,7 @@ func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value i
// bindVarToReflectValue sets `value` to reflect value object `structFieldValue`.
func bindVarToReflectValue(
structFieldValue reflect.Value, value interface{}, paramKeyToAttrMap map[string]string,
cf *ConvertConfig, structFieldValue reflect.Value, value any, paramKeyToAttrMap map[string]string,
) (err error) {
// JSON content converting.
ok, err := doConvertWithJsonCheck(value, structFieldValue)
@ -486,7 +505,7 @@ func bindVarToReflectValue(
// Converting using reflection by kind.
switch kind {
case reflect.Map:
return doMapToMap(value, structFieldValue, paramKeyToAttrMap)
return doMapToMap(cf, value, structFieldValue, paramKeyToAttrMap)
case reflect.Struct:
// Recursively converting for struct attribute.
@ -528,11 +547,13 @@ func bindVarToReflectValue(
}
}
if !converted {
doConvertWithReflectValueSet(elem, doConvertInput{
FromValue: reflectValue.Index(i).Interface(),
ToTypeName: elemTypeName,
ReferValue: elem,
})
doConvertWithReflectValueSet(
cf, elem, doConvertInput{
FromValue: reflectValue.Index(i).Interface(),
ToTypeName: elemTypeName,
ReferValue: elem,
},
)
}
if elemType.Kind() == reflect.Ptr {
// Before it sets the `elem` to array, do pointer converting if necessary.
@ -578,11 +599,13 @@ func bindVarToReflectValue(
}
}
if !converted {
doConvertWithReflectValueSet(elem, doConvertInput{
FromValue: value,
ToTypeName: elemTypeName,
ReferValue: elem,
})
doConvertWithReflectValueSet(
cf, elem, doConvertInput{
FromValue: value,
ToTypeName: elemTypeName,
ReferValue: elem,
},
)
}
if elemType.Kind() == reflect.Ptr {
// Before it sets the `elem` to array, do pointer converting if necessary.
@ -602,19 +625,19 @@ func bindVarToReflectValue(
return err
}
elem := item.Elem()
if err = bindVarToReflectValue(elem, value, paramKeyToAttrMap); err == nil {
if err = bindVarToReflectValue(cf, elem, value, paramKeyToAttrMap); err == nil {
structFieldValue.Set(elem.Addr())
}
} else {
// Not empty pointer, it assigns values to it.
return bindVarToReflectValue(structFieldValue.Elem(), value, paramKeyToAttrMap)
return bindVarToReflectValue(cf, structFieldValue.Elem(), value, paramKeyToAttrMap)
}
// It mainly and specially handles the interface of nil value.
case reflect.Interface:
if value == nil {
// Specially.
structFieldValue.Set(reflect.ValueOf((*interface{})(nil)))
structFieldValue.Set(reflect.ValueOf((*any)(nil)))
} else {
// Note there's reflect conversion mechanism here.
structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type()))

View File

@ -9,24 +9,64 @@ package structcache
import (
"reflect"
"sync"
"time"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv/internal/localinterface"
)
var (
type AnyConvertFunc func(from any, to reflect.Value) error
// ConvertConfig is the configuration for type converting.
type ConvertConfig struct {
// map[reflect.Type]*CachedStructInfo
cachedStructsInfoMap sync.Map
// customConvertTypeMap is used to store whether field types are registered to custom conversions
// For example:
// func (src *TypeA) (dst *TypeB,err error)
// This map will store `TypeB` for quick judgment during assignment.
customConvertTypeMap = map[reflect.Type]struct{}{}
)
// TODO remove?
customConvertTypeMap map[reflect.Type]struct{}
// anyToTypeConvertMap for custom type converting from any to its reflect.Value.
anyToTypeConvertMap map[reflect.Type]AnyConvertFunc
}
// CommonTypeConverter holds some converting functions of common types for internal usage.
type CommonTypeConverter struct {
Int64 func(v any) (int64, error)
Uint64 func(v any) (uint64, error)
String func(v any) (string, error)
Float32 func(v any) (float32, error)
Float64 func(v any) (float64, error)
Time func(v any, format ...string) (time.Time, error)
GTime func(v any, format ...string) (*gtime.Time, error)
Bytes func(v any) ([]byte, error)
Bool func(v any) (bool, error)
}
// NewConvertConfig creates and returns a new ConvertConfig object.
func NewConvertConfig() *ConvertConfig {
return &ConvertConfig{
cachedStructsInfoMap: sync.Map{},
customConvertTypeMap: make(map[reflect.Type]struct{}),
anyToTypeConvertMap: make(map[reflect.Type]AnyConvertFunc),
}
}
// RegisterCustomConvertType registers custom
func RegisterCustomConvertType(fieldType reflect.Type) {
func (cf *ConvertConfig) RegisterCustomConvertType(fieldType reflect.Type) {
if fieldType.Kind() == reflect.Ptr {
fieldType = fieldType.Elem()
}
customConvertTypeMap[fieldType] = struct{}{}
cf.customConvertTypeMap[fieldType] = struct{}{}
}
// RegisterAnyConvertFunc registers custom type converting function for specified type.
func (cf *ConvertConfig) RegisterAnyConvertFunc(t reflect.Type, convertFunc AnyConvertFunc) {
cf.anyToTypeConvertMap[t] = convertFunc
}
var (

View File

@ -8,48 +8,19 @@ package structcache
import (
"reflect"
"sync"
"time"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gtag"
)
// CommonConverter holds some converting functions of common types for internal usage.
type CommonConverter struct {
Int64 func(any interface{}) int64
Uint64 func(any interface{}) uint64
String func(any interface{}) string
Float32 func(any interface{}) float32
Float64 func(any interface{}) float64
Time func(any interface{}, format ...string) time.Time
GTime func(any interface{}, format ...string) *gtime.Time
Bytes func(any interface{}) []byte
Bool func(any interface{}) bool
}
var (
// map[reflect.Type]*CachedStructInfo
cachedStructsInfoMap = sync.Map{}
// localCommonConverter holds some converting functions of common types for internal usage.
localCommonConverter CommonConverter
)
// RegisterCommonConverter registers the CommonConverter for local usage.
func RegisterCommonConverter(commonConverter CommonConverter) {
localCommonConverter = commonConverter
}
// GetCachedStructInfo retrieves or parses and returns a cached info for certain struct type.
// The given `structType` should be type of struct.
func GetCachedStructInfo(structType reflect.Type, priorityTag string) *CachedStructInfo {
func (cf *ConvertConfig) GetCachedStructInfo(structType reflect.Type, priorityTag string) *CachedStructInfo {
if structType.Kind() != reflect.Struct {
return nil
}
// check if it has been cached.
cachedStructInfo, ok := getCachedConvertStructInfo(structType)
cachedStructInfo, ok := cf.getCachedConvertStructInfo(structType)
if ok {
// directly returns the cached struct info if already exists.
return cachedStructInfo
@ -58,9 +29,7 @@ func GetCachedStructInfo(structType reflect.Type, priorityTag string) *CachedStr
// else create one.
// it parses and generates a cache info for given struct type.
cachedStructInfo = &CachedStructInfo{
tagOrFiledNameToFieldInfoMap: make(map[string]*CachedFieldInfo),
}
cachedStructInfo = NewCachedStructInfo(cf.customConvertTypeMap, cf.anyToTypeConvertMap)
var (
priorityTagArray []string
parentIndex = make([]int, 0)
@ -70,19 +39,19 @@ func GetCachedStructInfo(structType reflect.Type, priorityTag string) *CachedStr
} else {
priorityTagArray = gtag.StructTagPriority
}
parseStructToCachedStructInfo(structType, parentIndex, cachedStructInfo, priorityTagArray)
storeCachedStructInfo(structType, cachedStructInfo)
cf.parseStructToCachedStructInfo(structType, parentIndex, cachedStructInfo, priorityTagArray)
cf.storeCachedStructInfo(structType, cachedStructInfo)
return cachedStructInfo
}
func storeCachedStructInfo(structType reflect.Type, cachedStructInfo *CachedStructInfo) {
func (cf *ConvertConfig) storeCachedStructInfo(structType reflect.Type, cachedStructInfo *CachedStructInfo) {
// Temporarily enabled as an experimental feature
cachedStructsInfoMap.Store(structType, cachedStructInfo)
cf.cachedStructsInfoMap.Store(structType, cachedStructInfo)
}
func getCachedConvertStructInfo(structType reflect.Type) (*CachedStructInfo, bool) {
func (cf *ConvertConfig) getCachedConvertStructInfo(structType reflect.Type) (*CachedStructInfo, bool) {
// Temporarily enabled as an experimental feature
v, ok := cachedStructsInfoMap.Load(structType)
v, ok := cf.cachedStructsInfoMap.Load(structType)
if ok {
return v.(*CachedStructInfo), ok
}
@ -91,7 +60,7 @@ func getCachedConvertStructInfo(structType reflect.Type) (*CachedStructInfo, boo
// parseStructToCachedStructInfo parses given struct reflection type and stores its fields info into given CachedStructInfo.
// It stores nothing into CachedStructInfo if given struct reflection type has no fields.
func parseStructToCachedStructInfo(
func (cf *ConvertConfig) parseStructToCachedStructInfo(
structType reflect.Type,
fieldIndexes []int,
cachedStructInfo *CachedStructInfo,
@ -136,7 +105,7 @@ func parseStructToCachedStructInfo(
// Do not add anonymous structures without tags
cachedStructInfo.AddField(structField, append(copyFieldIndexes, i), priorityTagArray)
}
parseStructToCachedStructInfo(fieldType, append(copyFieldIndexes, i), cachedStructInfo, priorityTagArray)
cf.parseStructToCachedStructInfo(fieldType, append(copyFieldIndexes, i), cachedStructInfo, priorityTagArray)
continue
}
// Do not directly use append(fieldIndexes, i)

View File

@ -76,7 +76,7 @@ type CachedFieldInfoBase struct {
OtherSameNameField []*CachedFieldInfo
// ConvertFunc is the converting function for this field.
ConvertFunc func(from any, to reflect.Value)
ConvertFunc AnyConvertFunc
// The last fuzzy matching key for this field.
// The fuzzy matching occurs only if there are no direct tag and field name matching in the params map.

View File

@ -9,14 +9,24 @@ package structcache
import (
"reflect"
"strings"
"time"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/os/gtime"
)
// CachedStructInfo holds the cached info for certain struct.
type CachedStructInfo struct {
// All sub attributes field info slice.
fieldConvertInfos []*CachedFieldInfo
// customConvertTypeMap is used to store whether field types are registered to custom conversions
// For example:
// func (src *TypeA) (dst *TypeB,err error)
// This map will store `TypeB` for quick judgment during assignment.
customConvertTypeMap map[reflect.Type]struct{}
// anyToTypeConvertMap for custom type converting from any to its reflect.Value.
anyToTypeConvertMap map[reflect.Type]AnyConvertFunc
// This map field is mainly used in the bindStructWithLoopParamsMap method
// key = field's name
// Will save all field names and PriorityTagAndFieldName
@ -25,9 +35,23 @@ type CachedStructInfo struct {
//
// It will be stored twice, which keys are `name` and `field`.
tagOrFiledNameToFieldInfoMap map[string]*CachedFieldInfo
}
// All sub attributes field info slice.
FieldConvertInfos []*CachedFieldInfo
// NewCachedStructInfo creates and returns a new CachedStructInfo object.
func NewCachedStructInfo(
customConvertTypeMap map[reflect.Type]struct{},
anyToTypeConvertMap map[reflect.Type]AnyConvertFunc,
) *CachedStructInfo {
return &CachedStructInfo{
tagOrFiledNameToFieldInfoMap: make(map[string]*CachedFieldInfo),
fieldConvertInfos: make([]*CachedFieldInfo, 0),
customConvertTypeMap: customConvertTypeMap,
anyToTypeConvertMap: anyToTypeConvertMap,
}
}
func (csi *CachedStructInfo) GetFieldConvertInfos() []*CachedFieldInfo {
return csi.fieldConvertInfos
}
func (csi *CachedStructInfo) HasNoFields() bool {
@ -46,7 +70,7 @@ func (csi *CachedStructInfo) AddField(field reflect.StructField, fieldIndexes []
field, fieldIndexes, priorityTags, cachedFieldInfo, tagOrFieldName,
)
if newFieldInfo.IsField {
csi.FieldConvertInfos = append(csi.FieldConvertInfos, newFieldInfo)
csi.fieldConvertInfos = append(csi.fieldConvertInfos, newFieldInfo)
}
// if the field info by `tagOrFieldName` already cached,
// it so adds this new field info to other same name field.
@ -82,7 +106,9 @@ func (csi *CachedStructInfo) makeOrCopyCachedInfo(
// copyCachedInfoWithFieldIndexes copies and returns a new CachedFieldInfo based on given CachedFieldInfo, but different
// FieldIndexes. Mainly used for copying fields with the same name and type.
func (csi *CachedStructInfo) copyCachedInfoWithFieldIndexes(cfi *CachedFieldInfo, fieldIndexes []int) *CachedFieldInfo {
func (csi *CachedStructInfo) copyCachedInfoWithFieldIndexes(
cfi *CachedFieldInfo, fieldIndexes []int,
) *CachedFieldInfo {
base := CachedFieldInfoBase{}
base = *cfi.CachedFieldInfoBase
base.FieldIndexes = fieldIndexes
@ -98,7 +124,7 @@ func (csi *CachedStructInfo) makeCachedFieldInfo(
IsCommonInterface: checkTypeIsCommonInterface(field),
StructField: field,
FieldIndexes: fieldIndexes,
ConvertFunc: csi.genFieldConvertFunc(field.Type.String()),
ConvertFunc: csi.genFieldConvertFunc(field.Type),
IsCustomConvert: csi.checkTypeHasCustomConvert(field.Type),
PriorityTagAndFieldName: csi.genPriorityTagAndFieldName(field, priorityTags),
RemoveSymbolsFieldName: utils.RemoveSymbols(field.Name),
@ -109,59 +135,20 @@ func (csi *CachedStructInfo) makeCachedFieldInfo(
}
}
func (csi *CachedStructInfo) genFieldConvertFunc(fieldType string) (convertFunc func(from any, to reflect.Value)) {
if fieldType[0] == '*' {
convertFunc = csi.genFieldConvertFunc(fieldType[1:])
func (csi *CachedStructInfo) genFieldConvertFunc(fieldType reflect.Type) (convertFunc AnyConvertFunc) {
if v := csi.anyToTypeConvertMap[fieldType]; v != nil {
return v
}
var fieldTypeKind = fieldType.Kind()
if fieldTypeKind == reflect.Ptr {
convertFunc = csi.genFieldConvertFunc(fieldType.Elem())
if convertFunc == nil {
return nil
}
return csi.genPtrConvertFunc(convertFunc)
}
switch fieldType {
case "int", "int8", "int16", "int32", "int64":
convertFunc = func(from any, to reflect.Value) {
to.SetInt(localCommonConverter.Int64(from))
}
case "uint", "uint8", "uint16", "uint32", "uint64":
convertFunc = func(from any, to reflect.Value) {
to.SetUint(localCommonConverter.Uint64(from))
}
case "string":
convertFunc = func(from any, to reflect.Value) {
to.SetString(localCommonConverter.String(from))
}
case "float32":
convertFunc = func(from any, to reflect.Value) {
to.SetFloat(float64(localCommonConverter.Float32(from)))
}
case "float64":
convertFunc = func(from any, to reflect.Value) {
to.SetFloat(localCommonConverter.Float64(from))
}
case "Time", "time.Time":
convertFunc = func(from any, to reflect.Value) {
*to.Addr().Interface().(*time.Time) = localCommonConverter.Time(from)
}
case "GTime", "gtime.Time":
convertFunc = func(from any, to reflect.Value) {
v := localCommonConverter.GTime(from)
if v == nil {
v = gtime.New()
}
*to.Addr().Interface().(*gtime.Time) = *v
}
case "bool":
convertFunc = func(from any, to reflect.Value) {
to.SetBool(localCommonConverter.Bool(from))
}
case "[]byte":
convertFunc = func(from any, to reflect.Value) {
to.SetBytes(localCommonConverter.Bytes(from))
}
default:
return nil
}
return convertFunc
return nil
}
func (csi *CachedStructInfo) genPriorityTagAndFieldName(
@ -188,21 +175,19 @@ func (csi *CachedStructInfo) genPriorityTagAndFieldName(
return
}
func (csi *CachedStructInfo) genPtrConvertFunc(convertFunc AnyConvertFunc) AnyConvertFunc {
return func(from any, to reflect.Value) error {
if to.IsNil() {
to.Set(reflect.New(to.Type().Elem()))
}
return convertFunc(from, to.Elem())
}
}
func (csi *CachedStructInfo) checkTypeHasCustomConvert(fieldType reflect.Type) bool {
if fieldType.Kind() == reflect.Ptr {
fieldType = fieldType.Elem()
}
_, ok := customConvertTypeMap[fieldType]
_, ok := csi.customConvertTypeMap[fieldType]
return ok
}
func (csi *CachedStructInfo) genPtrConvertFunc(
convertFunc func(from any, to reflect.Value),
) func(from any, to reflect.Value) {
return func(from any, to reflect.Value) {
if to.IsNil() {
to.Set(reflect.New(to.Type().Elem()))
}
convertFunc(from, to.Elem())
}
}