From d5e786ce930f4589dc27914bc3c49ce0ccdcbdee Mon Sep 17 00:00:00 2001 From: John Guo Date: Sun, 2 Mar 2025 13:09:40 +0800 Subject: [PATCH] up --- util/gconv/gconv.go | 15 +- util/gconv/gconv_basic.go | 281 +------- util/gconv/gconv_convert.go | 326 +-------- util/gconv/gconv_convert_config.go | 99 --- util/gconv/gconv_converter.go | 430 +++++++++++- util/gconv/gconv_converter_bool.go | 74 ++ util/gconv/gconv_converter_builtin.go | 39 +- util/gconv/gconv_converter_bytes.go | 67 ++ util/gconv/gconv_converter_float.go | 145 ++++ util/gconv/gconv_converter_int.go | 152 +++++ util/gconv/gconv_converter_map.go | 504 ++++++++++++++ util/gconv/gconv_converter_maps.go | 7 + util/gconv/gconv_converter_maptomap.go | 126 ++++ util/gconv/gconv_converter_maptomaps.go | 120 ++++ util/gconv/gconv_converter_rune.go | 29 + util/gconv/gconv_converter_scan.go | 336 +++++++++ util/gconv/gconv_converter_string.go | 135 ++++ util/gconv/gconv_converter_struct.go | 628 +++++++++++++++++ util/gconv/gconv_converter_structs.go | 115 ++++ util/gconv/gconv_converter_time.go | 111 +++ util/gconv/gconv_converter_uint.go | 156 +++++ util/gconv/gconv_float.go | 134 +--- util/gconv/gconv_int.go | 155 +---- util/gconv/gconv_map.go | 515 +------------- util/gconv/gconv_maptomap.go | 121 ---- util/gconv/gconv_maptomaps.go | 115 +--- util/gconv/gconv_scan.go | 332 +-------- util/gconv/gconv_struct.go | 635 +----------------- util/gconv/gconv_structs.go | 116 +--- util/gconv/gconv_time.go | 73 +- util/gconv/gconv_uint.go | 159 +---- util/gconv/gconv_z_bench_struct_test.go | 2 +- .../gconv/internal/structcache/structcache.go | 31 +- .../structcache/structcache_cached.go | 2 +- .../structcache_cached_struct_info.go | 14 +- 35 files changed, 3214 insertions(+), 3085 deletions(-) delete mode 100644 util/gconv/gconv_convert_config.go create mode 100644 util/gconv/gconv_converter_bool.go create mode 100644 util/gconv/gconv_converter_bytes.go create mode 100644 util/gconv/gconv_converter_float.go create mode 100644 util/gconv/gconv_converter_int.go create mode 100644 util/gconv/gconv_converter_map.go create mode 100644 util/gconv/gconv_converter_maps.go create mode 100644 util/gconv/gconv_converter_maptomap.go create mode 100644 util/gconv/gconv_converter_maptomaps.go create mode 100644 util/gconv/gconv_converter_rune.go create mode 100644 util/gconv/gconv_converter_scan.go create mode 100644 util/gconv/gconv_converter_string.go create mode 100644 util/gconv/gconv_converter_struct.go create mode 100644 util/gconv/gconv_converter_structs.go create mode 100644 util/gconv/gconv_converter_time.go create mode 100644 util/gconv/gconv_converter_uint.go diff --git a/util/gconv/gconv.go b/util/gconv/gconv.go index 426afd401..00748eb06 100644 --- a/util/gconv/gconv.go +++ b/util/gconv/gconv.go @@ -32,6 +32,17 @@ var ( type IUnmarshalValue = localinterface.IUnmarshalValue var ( - // defaultConvertConfig is the default configuration for type converting. - defaultConvertConfig = NewConvertConfig() + // defaultConverter is the default management object converting. + defaultConverter = NewConverter() ) + +// RegisterConverter registers custom converter. +// Deprecated: use RegisterTypeConverterFunc instead for clear +func RegisterConverter(fn any) (err error) { + return defaultConverter.RegisterTypeConverterFunc(fn) +} + +// RegisterTypeConverterFunc registers custom converter. +func RegisterTypeConverterFunc(fn any) (err error) { + return defaultConverter.RegisterTypeConverterFunc(fn) +} diff --git a/util/gconv/gconv_basic.go b/util/gconv/gconv_basic.go index 03449231e..d4ab35eaf 100644 --- a/util/gconv/gconv_basic.go +++ b/util/gconv/gconv_basic.go @@ -6,309 +6,40 @@ package gconv -import ( - "fmt" - "math" - "reflect" - "strconv" - "strings" - "time" - - "github.com/gogf/gf/v2/encoding/gbinary" - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/internal/empty" - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/internal/reflection" - "github.com/gogf/gf/v2/os/gtime" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" -) - // Byte converts `any` to byte. func Byte(any any) byte { - v, _ := doByte(any) + v, _ := defaultConverter.Uint8(any) return v } -func doByte(any any) (byte, error) { - if v, ok := any.(byte); ok { - return v, nil - } - return doUint8(any) -} - // Bytes converts `any` to []byte. func Bytes(any any) []byte { - v, _ := doBytes(any) + v, _ := defaultConverter.Bytes(any) return v } -func doBytes(any any) ([]byte, error) { - if empty.IsNil(any) { - return nil, nil - } - switch value := any.(type) { - case string: - return []byte(value), nil - - case []byte: - return value, nil - - default: - if f, ok := value.(localinterface.IBytes); ok { - return f.Bytes(), nil - } - originValueAndKind := reflection.OriginValueAndKind(any) - switch originValueAndKind.OriginKind { - case reflect.Map: - bytes, err := json.Marshal(any) - if err != nil { - return nil, err - } - return bytes, nil - - case reflect.Array, reflect.Slice: - var ( - ok = true - bytes = make([]byte, originValueAndKind.OriginValue.Len()) - ) - for i := range bytes { - int32Value, err := doInt32(originValueAndKind.OriginValue.Index(i).Interface()) - if err != nil { - return nil, err - } - if int32Value < 0 || int32Value > math.MaxUint8 { - ok = false - break - } - bytes[i] = byte(int32Value) - } - if ok { - return bytes, nil - } - default: - } - return gbinary.Encode(any), nil - } -} - // Rune converts `any` to rune. func Rune(any any) rune { - v, _ := doRune(any) + v, _ := defaultConverter.Rune(any) return v } -func doRune(any any) (rune, error) { - if v, ok := any.(rune); ok { - return v, nil - } - v, err := doInt32(any) - if err != nil { - return 0, err - } - return v, nil -} - // Runes converts `any` to []rune. func Runes(any any) []rune { - v, _ := doRunes(any) + v, _ := defaultConverter.Runes(any) return v } -func doRunes(any any) ([]rune, error) { - if v, ok := any.([]rune); ok { - return v, nil - } - s, err := doString(any) - if err != nil { - return nil, err - } - return []rune(s), nil -} - // String converts `any` to string. // It's most commonly used converting function. func String(any any) string { - v, _ := doString(any) + v, _ := defaultConverter.String(any) return v } -func doString(any any) (string, error) { - if empty.IsNil(any) { - return "", nil - } - switch value := any.(type) { - case int: - return strconv.Itoa(value), nil - case int8: - return strconv.Itoa(int(value)), nil - case int16: - return strconv.Itoa(int(value)), nil - case int32: - return strconv.Itoa(int(value)), nil - case int64: - return strconv.FormatInt(value, 10), nil - case uint: - return strconv.FormatUint(uint64(value), 10), nil - case uint8: - return strconv.FormatUint(uint64(value), 10), nil - case uint16: - return strconv.FormatUint(uint64(value), 10), nil - case uint32: - return strconv.FormatUint(uint64(value), 10), nil - case uint64: - return strconv.FormatUint(value, 10), nil - case float32: - return strconv.FormatFloat(float64(value), 'f', -1, 32), nil - case float64: - return strconv.FormatFloat(value, 'f', -1, 64), nil - case bool: - return strconv.FormatBool(value), nil - case string: - return value, nil - case []byte: - return string(value), nil - case complex64, complex128: - return fmt.Sprintf("%v", value), nil - case time.Time: - if value.IsZero() { - return "", nil - } - return value.String(), nil - case *time.Time: - if value == nil { - return "", nil - } - return value.String(), nil - case gtime.Time: - if value.IsZero() { - return "", nil - } - return value.String(), nil - case *gtime.Time: - if value == nil { - return "", nil - } - return value.String(), nil - default: - if f, ok := value.(localinterface.IString); ok { - // If the variable implements the String() interface, - // then use that interface to perform the conversion - return f.String(), nil - } - if f, ok := value.(localinterface.IError); ok { - // If the variable implements the Error() interface, - // then use that interface to perform the conversion - return f.Error(), nil - } - // Reflect checks. - var ( - rv = reflect.ValueOf(value) - kind = rv.Kind() - ) - switch kind { - case - reflect.Chan, - reflect.Map, - reflect.Slice, - reflect.Func, - reflect.Interface, - reflect.UnsafePointer: - if rv.IsNil() { - return "", nil - } - case reflect.String: - return rv.String(), nil - case reflect.Ptr: - if rv.IsNil() { - return "", nil - } - return doString(rv.Elem().Interface()) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return strconv.FormatInt(rv.Int(), 10), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return strconv.FormatUint(rv.Uint(), 10), nil - case reflect.Uintptr: - return strconv.FormatUint(rv.Uint(), 10), nil - case reflect.Float32, reflect.Float64: - return strconv.FormatFloat(rv.Float(), 'f', -1, 64), nil - case reflect.Bool: - return strconv.FormatBool(rv.Bool()), nil - default: - } - // Finally, we use json.Marshal to convert. - jsonContent, err := json.Marshal(value) - if err != nil { - return fmt.Sprint(value), gerror.WrapCodef( - gcode.CodeInvalidParameter, err, "error marshaling value to JSON for: %v", value, - ) - } - return string(jsonContent), nil - } -} - // Bool converts `any` to bool. // It returns false if `any` is: false, "", 0, "false", "off", "no", empty slice/map. func Bool(any any) bool { - v, _ := doBool(any) + v, _ := defaultConverter.Bool(any) return v } - -func doBool(any any) (bool, error) { - if empty.IsNil(any) { - return false, nil - } - switch value := any.(type) { - case bool: - return value, nil - case []byte: - if _, ok := emptyStringMap[strings.ToLower(string(value))]; ok { - return false, nil - } - return true, nil - case string: - if _, ok := emptyStringMap[strings.ToLower(value)]; ok { - return false, nil - } - return true, nil - default: - if f, ok := value.(localinterface.IBool); ok { - return f.Bool(), nil - } - rv := reflect.ValueOf(any) - switch rv.Kind() { - case reflect.Ptr: - if rv.IsNil() { - return false, nil - } - if rv.Type().Elem().Kind() == reflect.Bool { - return rv.Elem().Bool(), nil - } - return doBool(rv.Elem().Interface()) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return rv.Int() != 0, nil - case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return rv.Uint() != 0, nil - case reflect.Float32, reflect.Float64: - return rv.Float() != 0, nil - case reflect.Bool: - return rv.Bool(), nil - // TODO:(Map,Array,Slice,Struct) It might panic here for these types. - case reflect.Map, reflect.Array: - fallthrough - case reflect.Slice: - return rv.Len() != 0, nil - case reflect.Struct: - return true, nil - default: - s, err := doString(any) - if err != nil { - return false, err - } - if _, ok := emptyStringMap[strings.ToLower(s)]; ok { - return false, nil - } - return true, nil - } - } -} diff --git a/util/gconv/gconv_convert.go b/util/gconv/gconv_convert.go index 2cd67d3e8..6603469a2 100644 --- a/util/gconv/gconv_convert.go +++ b/util/gconv/gconv_convert.go @@ -7,22 +7,15 @@ package gconv import ( - "context" "reflect" - "time" - - "github.com/gogf/gf/v2/internal/intlog" - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/os/gtime" ) // 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 basic types conversion as its conversion based on type name string. -func Convert(fromValue interface{}, toTypeName string, extraParams ...interface{}) interface{} { - return doConvert( - defaultConvertConfig, +func Convert(fromValue any, toTypeName string, extraParams ...any) any { + return defaultConverter.doConvert( doConvertInput{ FromValue: fromValue, ToTypeName: toTypeName, @@ -36,15 +29,14 @@ func Convert(fromValue interface{}, toTypeName string, extraParams ...interface{ // // 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 ConvertWithRefer(fromValue interface{}, referValue interface{}, extraParams ...interface{}) interface{} { +func ConvertWithRefer(fromValue any, referValue any, extraParams ...any) any { var referValueRf reflect.Value if v, ok := referValue.(reflect.Value); ok { referValueRf = v } else { referValueRf = reflect.ValueOf(referValue) } - return doConvert( - defaultConvertConfig, + return defaultConverter.doConvert( doConvertInput{ FromValue: fromValue, ToTypeName: referValueRf.Type().String(), @@ -53,313 +45,3 @@ func ConvertWithRefer(fromValue interface{}, referValue interface{}, extraParams }, ) } - -type doConvertInput struct { - FromValue interface{} // Value that is converted from. - 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(cf *ConvertConfig, in doConvertInput) (convertedValue interface{}) { - switch in.ToTypeName { - case "int": - return Int(in.FromValue) - case "*int": - if _, ok := in.FromValue.(*int); ok { - return in.FromValue - } - v := Int(in.FromValue) - return &v - - case "int8": - return Int8(in.FromValue) - case "*int8": - if _, ok := in.FromValue.(*int8); ok { - return in.FromValue - } - v := Int8(in.FromValue) - return &v - - case "int16": - return Int16(in.FromValue) - case "*int16": - if _, ok := in.FromValue.(*int16); ok { - return in.FromValue - } - v := Int16(in.FromValue) - return &v - - case "int32": - return Int32(in.FromValue) - case "*int32": - if _, ok := in.FromValue.(*int32); ok { - return in.FromValue - } - v := Int32(in.FromValue) - return &v - - case "int64": - return Int64(in.FromValue) - case "*int64": - if _, ok := in.FromValue.(*int64); ok { - return in.FromValue - } - v := Int64(in.FromValue) - return &v - - case "uint": - return Uint(in.FromValue) - case "*uint": - if _, ok := in.FromValue.(*uint); ok { - return in.FromValue - } - v := Uint(in.FromValue) - return &v - - case "uint8": - return Uint8(in.FromValue) - case "*uint8": - if _, ok := in.FromValue.(*uint8); ok { - return in.FromValue - } - v := Uint8(in.FromValue) - return &v - - case "uint16": - return Uint16(in.FromValue) - case "*uint16": - if _, ok := in.FromValue.(*uint16); ok { - return in.FromValue - } - v := Uint16(in.FromValue) - return &v - - case "uint32": - return Uint32(in.FromValue) - case "*uint32": - if _, ok := in.FromValue.(*uint32); ok { - return in.FromValue - } - v := Uint32(in.FromValue) - return &v - - case "uint64": - return Uint64(in.FromValue) - case "*uint64": - if _, ok := in.FromValue.(*uint64); ok { - return in.FromValue - } - v := Uint64(in.FromValue) - return &v - - case "float32": - return Float32(in.FromValue) - case "*float32": - if _, ok := in.FromValue.(*float32); ok { - return in.FromValue - } - v := Float32(in.FromValue) - return &v - - case "float64": - return Float64(in.FromValue) - case "*float64": - if _, ok := in.FromValue.(*float64); ok { - return in.FromValue - } - v := Float64(in.FromValue) - return &v - - case "bool": - return Bool(in.FromValue) - case "*bool": - if _, ok := in.FromValue.(*bool); ok { - return in.FromValue - } - v := Bool(in.FromValue) - return &v - - case "string": - return String(in.FromValue) - case "*string": - if _, ok := in.FromValue.(*string); ok { - return in.FromValue - } - v := String(in.FromValue) - return &v - - case "[]byte": - return Bytes(in.FromValue) - case "[]int": - return Ints(in.FromValue) - case "[]int32": - return Int32s(in.FromValue) - case "[]int64": - return Int64s(in.FromValue) - case "[]uint": - return Uints(in.FromValue) - case "[]uint8": - return Bytes(in.FromValue) - case "[]uint32": - return Uint32s(in.FromValue) - case "[]uint64": - return Uint64s(in.FromValue) - case "[]float32": - return Float32s(in.FromValue) - case "[]float64": - return Float64s(in.FromValue) - case "[]string": - return Strings(in.FromValue) - - case "Time", "time.Time": - if len(in.Extra) > 0 { - return Time(in.FromValue, String(in.Extra[0])) - } - return Time(in.FromValue) - case "*time.Time": - var v time.Time - if len(in.Extra) > 0 { - v = Time(in.FromValue, String(in.Extra[0])) - } else { - if _, ok := in.FromValue.(*time.Time); ok { - return in.FromValue - } - v = Time(in.FromValue) - } - return &v - - case "GTime", "gtime.Time": - if len(in.Extra) > 0 { - if v := GTime(in.FromValue, String(in.Extra[0])); v != nil { - return *v - } else { - return *gtime.New() - } - } - if v := GTime(in.FromValue); v != nil { - return *v - } else { - return *gtime.New() - } - case "*gtime.Time": - if len(in.Extra) > 0 { - if v := GTime(in.FromValue, String(in.Extra[0])); v != nil { - return v - } else { - return gtime.New() - } - } - if v := GTime(in.FromValue); v != nil { - return v - } else { - return gtime.New() - } - - case "Duration", "time.Duration": - return Duration(in.FromValue) - case "*time.Duration": - if _, ok := in.FromValue.(*time.Duration); ok { - return in.FromValue - } - v := Duration(in.FromValue) - return &v - - case "map[string]string": - return MapStrStr(in.FromValue) - - case "map[string]interface {}": - return Map(in.FromValue) - - case "[]map[string]interface {}": - return Maps(in.FromValue) - - case "RawMessage", "json.RawMessage": - // issue 3449 - bytes, err := json.Marshal(in.FromValue) - if err != nil { - intlog.Errorf(context.TODO(), `%+v`, err) - } - return bytes - - default: - if in.ReferValue != nil { - var referReflectValue reflect.Value - if v, ok := in.ReferValue.(reflect.Value); ok { - referReflectValue = v - } else { - referReflectValue = reflect.ValueOf(in.ReferValue) - } - var fromReflectValue reflect.Value - if v, ok := in.FromValue.(reflect.Value); ok { - fromReflectValue = v - } else { - fromReflectValue = reflect.ValueOf(in.FromValue) - } - - // custom converter. - if dstReflectValue, ok, _ := cf.callCustomConverterWithRefer(fromReflectValue, referReflectValue); ok { - return dstReflectValue.Interface() - } - - defer func() { - if recover() != nil { - in.alreadySetToReferValue = false - if err := bindVarToReflectValue(cf, referReflectValue, in.FromValue, nil); err == nil { - in.alreadySetToReferValue = true - convertedValue = referReflectValue.Interface() - } - } - }() - switch referReflectValue.Kind() { - case reflect.Ptr: - // Type converting for custom type pointers. - // Eg: - // type PayMode int - // type Req struct{ - // Mode *PayMode - // } - // - // Struct(`{"Mode": 1000}`, &req) - originType := referReflectValue.Type().Elem() - switch originType.Kind() { - case reflect.Struct: - // Not support some kinds. - default: - in.ToTypeName = originType.Kind().String() - in.ReferValue = nil - refElementValue := reflect.ValueOf(doConvert(cf, in)) - originTypeValue := reflect.New(refElementValue.Type()).Elem() - originTypeValue.Set(refElementValue) - in.alreadySetToReferValue = true - return originTypeValue.Addr().Convert(referReflectValue.Type()).Interface() - } - - case reflect.Map: - var targetValue = reflect.New(referReflectValue.Type()).Elem() - if err := doMapToMap(cf, in.FromValue, targetValue); err == nil { - in.alreadySetToReferValue = true - } - return targetValue.Interface() - } - in.ToTypeName = referReflectValue.Kind().String() - in.ReferValue = nil - in.alreadySetToReferValue = true - convertedValue = reflect.ValueOf(doConvert(cf, in)).Convert(referReflectValue.Type()).Interface() - return convertedValue - } - return in.FromValue - } -} - -func doConvertWithReflectValueSet(cf *ConvertConfig, reflectValue reflect.Value, in doConvertInput) { - convertedValue := doConvert(cf, in) - if !in.alreadySetToReferValue { - reflectValue.Set(reflect.ValueOf(convertedValue)) - } -} diff --git a/util/gconv/gconv_convert_config.go b/util/gconv/gconv_convert_config.go deleted file mode 100644 index 82712c077..000000000 --- a/util/gconv/gconv_convert_config.go +++ /dev/null @@ -1,99 +0,0 @@ -// 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) - } -} diff --git a/util/gconv/gconv_converter.go b/util/gconv/gconv_converter.go index 72337f00f..2a4803cd3 100644 --- a/util/gconv/gconv_converter.go +++ b/util/gconv/gconv_converter.go @@ -7,18 +7,99 @@ package gconv import ( + "context" "reflect" + "time" "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/intlog" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv/internal/structcache" ) -// RegisterConverter registers custom converter. -func RegisterConverter(fn any) (err error) { - return defaultConvertConfig.RegisterConverter(fn) +type ( + converterInType = reflect.Type + converterOutType = reflect.Type + converterFunc = reflect.Value +) + +// Converter is the manager for type converting. +type Converter struct { + internalConvertConfig *structcache.ConvertConfig + typeConverterFuncMap map[converterInType]map[converterOutType]converterFunc } -// RegisterConverter registers custom converter. +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() +) + +// NewConverter creates and returns management object for type converting. +func NewConverter() *Converter { + cf := &Converter{ + internalConvertConfig: structcache.NewConvertConfig(), + typeConverterFuncMap: make(map[converterInType]map[converterOutType]converterFunc), + } + cf.registerBuiltInConverter() + return cf +} + +func (c *Converter) registerBuiltInConverter() { + c.registerAnyConvertFuncForTypes( + c.builtInAnyConvertFuncForInt64, intType, int8Type, int16Type, int32Type, int64Type, + ) + c.registerAnyConvertFuncForTypes( + c.builtInAnyConvertFuncForUint64, uintType, uint8Type, uint16Type, uint32Type, uint64Type, + ) + c.registerAnyConvertFuncForTypes( + c.builtInAnyConvertFuncForString, stringType, + ) + c.registerAnyConvertFuncForTypes( + c.builtInAnyConvertFuncForFloat64, float32Type, float64Type, + ) + c.registerAnyConvertFuncForTypes( + c.builtInAnyConvertFuncForBool, boolType, + ) + c.registerAnyConvertFuncForTypes( + c.builtInAnyConvertFuncForBytes, bytesType, + ) + c.registerAnyConvertFuncForTypes( + c.builtInAnyConvertFuncForTime, timeType, + ) + c.registerAnyConvertFuncForTypes( + c.builtInAnyConvertFuncForGTime, gtimeType, + ) +} + +func (c *Converter) registerAnyConvertFuncForTypes(convertFunc AnyConvertFunc, types ...reflect.Type) { + for _, t := range types { + c.internalConvertConfig.RegisterAnyConvertFunc(t, convertFunc) + } +} + +// RegisterTypeConverterFunc 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. // @@ -26,7 +107,7 @@ func RegisterConverter(fn any) (err error) { // 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 (cf *ConvertConfig) RegisterConverter(fn any) (err error) { +func (c *Converter) RegisterTypeConverterFunc(fn any) (err error) { var ( fnReflectType = reflect.TypeOf(fn) errType = reflect.TypeOf((*error)(nil)).Elem() @@ -65,10 +146,10 @@ func (cf *ConvertConfig) RegisterConverter(fn any) (err error) { return } - registeredOutTypeMap, ok := cf.customConverters[inType] + registeredOutTypeMap, ok := c.typeConverterFuncMap[inType] if !ok { registeredOutTypeMap = make(map[converterOutType]converterFunc) - cf.customConverters[inType] = registeredOutTypeMap + c.typeConverterFuncMap[inType] = registeredOutTypeMap } if _, ok = registeredOutTypeMap[outType]; ok { err = gerror.NewCodef( @@ -79,14 +160,14 @@ func (cf *ConvertConfig) RegisterConverter(fn any) (err error) { return } registeredOutTypeMap[outType] = reflect.ValueOf(fn) - cf.internalConvertConfig.RegisterCustomConvertType(outType) + c.internalConvertConfig.RegisterTypeConvertFunc(outType) return } -func (cf *ConvertConfig) getRegisteredConverterFuncAndSrcType( +func (c *Converter) getRegisteredConverterFuncAndSrcType( srcReflectValue, dstReflectValueForRefer reflect.Value, ) (f converterFunc, srcType reflect.Type, ok bool) { - if len(cf.customConverters) == 0 { + if len(c.typeConverterFuncMap) == 0 { return reflect.Value{}, nil, false } srcType = srcReflectValue.Type() @@ -95,7 +176,7 @@ func (cf *ConvertConfig) getRegisteredConverterFuncAndSrcType( } var registeredOutTypeMap map[converterOutType]converterFunc // firstly, it searches the map by input parameter type. - registeredOutTypeMap, ok = cf.customConverters[srcType] + registeredOutTypeMap, ok = c.typeConverterFuncMap[srcType] if !ok { return reflect.Value{}, nil, false } @@ -119,28 +200,28 @@ func (cf *ConvertConfig) getRegisteredConverterFuncAndSrcType( return } -func (cf *ConvertConfig) callCustomConverterWithRefer( +func (c *Converter) callCustomConverterWithRefer( srcReflectValue, referReflectValue reflect.Value, ) (dstReflectValue reflect.Value, converted bool, err error) { - registeredConverterFunc, srcType, ok := cf.getRegisteredConverterFuncAndSrcType(srcReflectValue, referReflectValue) + registeredConverterFunc, srcType, ok := c.getRegisteredConverterFuncAndSrcType(srcReflectValue, referReflectValue) if !ok { return reflect.Value{}, false, nil } dstReflectValue = reflect.New(referReflectValue.Type()).Elem() - converted, err = cf.doCallCustomConverter(srcReflectValue, dstReflectValue, registeredConverterFunc, srcType) + converted, err = c.doCallCustomConverter(srcReflectValue, dstReflectValue, registeredConverterFunc, srcType) return } // callCustomConverter call the custom converter. It will try some possible type. -func (cf *ConvertConfig) callCustomConverter(srcReflectValue, dstReflectValue reflect.Value) (converted bool, err error) { - registeredConverterFunc, srcType, ok := cf.getRegisteredConverterFuncAndSrcType(srcReflectValue, dstReflectValue) +func (c *Converter) callCustomConverter(srcReflectValue, dstReflectValue reflect.Value) (converted bool, err error) { + registeredConverterFunc, srcType, ok := c.getRegisteredConverterFuncAndSrcType(srcReflectValue, dstReflectValue) if !ok { return false, nil } - return cf.doCallCustomConverter(srcReflectValue, dstReflectValue, registeredConverterFunc, srcType) + return c.doCallCustomConverter(srcReflectValue, dstReflectValue, registeredConverterFunc, srcType) } -func (cf *ConvertConfig) doCallCustomConverter( +func (c *Converter) doCallCustomConverter( srcReflectValue reflect.Value, dstReflectValue reflect.Value, registeredConverterFunc converterFunc, @@ -181,3 +262,316 @@ func (cf *ConvertConfig) doCallCustomConverter( return converted, nil } + +type doConvertInput struct { + FromValue interface{} // Value that is converted from. + 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 (c *Converter) doConvert(in doConvertInput) (convertedValue interface{}) { + switch in.ToTypeName { + case "int": + return Int(in.FromValue) + case "*int": + if _, ok := in.FromValue.(*int); ok { + return in.FromValue + } + v := Int(in.FromValue) + return &v + + case "int8": + return Int8(in.FromValue) + case "*int8": + if _, ok := in.FromValue.(*int8); ok { + return in.FromValue + } + v := Int8(in.FromValue) + return &v + + case "int16": + return Int16(in.FromValue) + case "*int16": + if _, ok := in.FromValue.(*int16); ok { + return in.FromValue + } + v := Int16(in.FromValue) + return &v + + case "int32": + return Int32(in.FromValue) + case "*int32": + if _, ok := in.FromValue.(*int32); ok { + return in.FromValue + } + v := Int32(in.FromValue) + return &v + + case "int64": + return Int64(in.FromValue) + case "*int64": + if _, ok := in.FromValue.(*int64); ok { + return in.FromValue + } + v := Int64(in.FromValue) + return &v + + case "uint": + return Uint(in.FromValue) + case "*uint": + if _, ok := in.FromValue.(*uint); ok { + return in.FromValue + } + v := Uint(in.FromValue) + return &v + + case "uint8": + return Uint8(in.FromValue) + case "*uint8": + if _, ok := in.FromValue.(*uint8); ok { + return in.FromValue + } + v := Uint8(in.FromValue) + return &v + + case "uint16": + return Uint16(in.FromValue) + case "*uint16": + if _, ok := in.FromValue.(*uint16); ok { + return in.FromValue + } + v := Uint16(in.FromValue) + return &v + + case "uint32": + return Uint32(in.FromValue) + case "*uint32": + if _, ok := in.FromValue.(*uint32); ok { + return in.FromValue + } + v := Uint32(in.FromValue) + return &v + + case "uint64": + return Uint64(in.FromValue) + case "*uint64": + if _, ok := in.FromValue.(*uint64); ok { + return in.FromValue + } + v := Uint64(in.FromValue) + return &v + + case "float32": + return Float32(in.FromValue) + case "*float32": + if _, ok := in.FromValue.(*float32); ok { + return in.FromValue + } + v := Float32(in.FromValue) + return &v + + case "float64": + return Float64(in.FromValue) + case "*float64": + if _, ok := in.FromValue.(*float64); ok { + return in.FromValue + } + v := Float64(in.FromValue) + return &v + + case "bool": + return Bool(in.FromValue) + case "*bool": + if _, ok := in.FromValue.(*bool); ok { + return in.FromValue + } + v := Bool(in.FromValue) + return &v + + case "string": + return String(in.FromValue) + case "*string": + if _, ok := in.FromValue.(*string); ok { + return in.FromValue + } + v := String(in.FromValue) + return &v + + case "[]byte": + return Bytes(in.FromValue) + case "[]int": + return Ints(in.FromValue) + case "[]int32": + return Int32s(in.FromValue) + case "[]int64": + return Int64s(in.FromValue) + case "[]uint": + return Uints(in.FromValue) + case "[]uint8": + return Bytes(in.FromValue) + case "[]uint32": + return Uint32s(in.FromValue) + case "[]uint64": + return Uint64s(in.FromValue) + case "[]float32": + return Float32s(in.FromValue) + case "[]float64": + return Float64s(in.FromValue) + case "[]string": + return Strings(in.FromValue) + + case "Time", "time.Time": + if len(in.Extra) > 0 { + return Time(in.FromValue, String(in.Extra[0])) + } + return Time(in.FromValue) + case "*time.Time": + var v time.Time + if len(in.Extra) > 0 { + v = Time(in.FromValue, String(in.Extra[0])) + } else { + if _, ok := in.FromValue.(*time.Time); ok { + return in.FromValue + } + v = Time(in.FromValue) + } + return &v + + case "GTime", "gtime.Time": + if len(in.Extra) > 0 { + if v := GTime(in.FromValue, String(in.Extra[0])); v != nil { + return *v + } else { + return *gtime.New() + } + } + if v := GTime(in.FromValue); v != nil { + return *v + } else { + return *gtime.New() + } + case "*gtime.Time": + if len(in.Extra) > 0 { + if v := GTime(in.FromValue, String(in.Extra[0])); v != nil { + return v + } else { + return gtime.New() + } + } + if v := GTime(in.FromValue); v != nil { + return v + } else { + return gtime.New() + } + + case "Duration", "time.Duration": + return Duration(in.FromValue) + case "*time.Duration": + if _, ok := in.FromValue.(*time.Duration); ok { + return in.FromValue + } + v := Duration(in.FromValue) + return &v + + case "map[string]string": + return MapStrStr(in.FromValue) + + case "map[string]interface {}": + return Map(in.FromValue) + + case "[]map[string]interface {}": + return Maps(in.FromValue) + + case "RawMessage", "json.RawMessage": + // issue 3449 + bytes, err := json.Marshal(in.FromValue) + if err != nil { + intlog.Errorf(context.TODO(), `%+v`, err) + } + return bytes + + default: + if in.ReferValue != nil { + var referReflectValue reflect.Value + if v, ok := in.ReferValue.(reflect.Value); ok { + referReflectValue = v + } else { + referReflectValue = reflect.ValueOf(in.ReferValue) + } + var fromReflectValue reflect.Value + if v, ok := in.FromValue.(reflect.Value); ok { + fromReflectValue = v + } else { + fromReflectValue = reflect.ValueOf(in.FromValue) + } + + // custom converter. + if dstReflectValue, ok, _ := c.callCustomConverterWithRefer(fromReflectValue, referReflectValue); ok { + return dstReflectValue.Interface() + } + + defer func() { + if recover() != nil { + in.alreadySetToReferValue = false + if err := c.bindVarToReflectValue(referReflectValue, in.FromValue, nil); err == nil { + in.alreadySetToReferValue = true + convertedValue = referReflectValue.Interface() + } + } + }() + switch referReflectValue.Kind() { + case reflect.Ptr: + // Type converting for custom type pointers. + // Eg: + // type PayMode int + // type Req struct{ + // Mode *PayMode + // } + // + // Struct(`{"Mode": 1000}`, &req) + originType := referReflectValue.Type().Elem() + switch originType.Kind() { + case reflect.Struct: + // Not support some kinds. + default: + in.ToTypeName = originType.Kind().String() + in.ReferValue = nil + refElementValue := reflect.ValueOf(c.doConvert(in)) + originTypeValue := reflect.New(refElementValue.Type()).Elem() + originTypeValue.Set(refElementValue) + in.alreadySetToReferValue = true + return originTypeValue.Addr().Convert(referReflectValue.Type()).Interface() + } + + case reflect.Map: + var targetValue = reflect.New(referReflectValue.Type()).Elem() + if err := c.MapToMap(in.FromValue, targetValue); err == nil { + in.alreadySetToReferValue = true + } + return targetValue.Interface() + + default: + + } + in.ToTypeName = referReflectValue.Kind().String() + in.ReferValue = nil + in.alreadySetToReferValue = true + convertedValue = reflect.ValueOf(c.doConvert(in)).Convert(referReflectValue.Type()).Interface() + return convertedValue + } + return in.FromValue + } +} + +func (c *Converter) doConvertWithReflectValueSet(reflectValue reflect.Value, in doConvertInput) { + convertedValue := c.doConvert(in) + if !in.alreadySetToReferValue { + reflectValue.Set(reflect.ValueOf(convertedValue)) + } +} diff --git a/util/gconv/gconv_converter_bool.go b/util/gconv/gconv_converter_bool.go new file mode 100644 index 000000000..94e26bea2 --- /dev/null +++ b/util/gconv/gconv_converter_bool.go @@ -0,0 +1,74 @@ +// 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" + "strings" + + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +func (c *Converter) Bool(any any) (bool, error) { + if empty.IsNil(any) { + return false, nil + } + switch value := any.(type) { + case bool: + return value, nil + case []byte: + if _, ok := emptyStringMap[strings.ToLower(string(value))]; ok { + return false, nil + } + return true, nil + case string: + if _, ok := emptyStringMap[strings.ToLower(value)]; ok { + return false, nil + } + return true, nil + default: + if f, ok := value.(localinterface.IBool); ok { + return f.Bool(), nil + } + rv := reflect.ValueOf(any) + switch rv.Kind() { + case reflect.Ptr: + if rv.IsNil() { + return false, nil + } + if rv.Type().Elem().Kind() == reflect.Bool { + return rv.Elem().Bool(), nil + } + return c.Bool(rv.Elem().Interface()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return rv.Int() != 0, nil + case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return rv.Uint() != 0, nil + case reflect.Float32, reflect.Float64: + return rv.Float() != 0, nil + case reflect.Bool: + return rv.Bool(), nil + // TODO:(Map,Array,Slice,Struct) It might panic here for these types. + case reflect.Map, reflect.Array: + fallthrough + case reflect.Slice: + return rv.Len() != 0, nil + case reflect.Struct: + return true, nil + default: + s, err := c.String(any) + if err != nil { + return false, err + } + if _, ok := emptyStringMap[strings.ToLower(s)]; ok { + return false, nil + } + return true, nil + } + } +} diff --git a/util/gconv/gconv_converter_builtin.go b/util/gconv/gconv_converter_builtin.go index 1d2e00594..5009e6d46 100644 --- a/util/gconv/gconv_converter_builtin.go +++ b/util/gconv/gconv_converter_builtin.go @@ -13,8 +13,8 @@ import ( "github.com/gogf/gf/v2/os/gtime" ) -func builtInAnyConvertFuncForInt64(from any, to reflect.Value) error { - v, err := doInt64(from) +func (c *Converter) builtInAnyConvertFuncForInt64(from any, to reflect.Value) error { + v, err := c.Int64(from) if err != nil { return err } @@ -22,8 +22,8 @@ func builtInAnyConvertFuncForInt64(from any, to reflect.Value) error { return nil } -func builtInAnyConvertFuncForUint64(from any, to reflect.Value) error { - v, err := doUint64(from) +func (c *Converter) builtInAnyConvertFuncForUint64(from any, to reflect.Value) error { + v, err := c.Uint64(from) if err != nil { return err } @@ -31,8 +31,8 @@ func builtInAnyConvertFuncForUint64(from any, to reflect.Value) error { return nil } -func builtInAnyConvertFuncForString(from any, to reflect.Value) error { - v, err := doString(from) +func (c *Converter) builtInAnyConvertFuncForString(from any, to reflect.Value) error { + v, err := c.String(from) if err != nil { return err } @@ -40,8 +40,8 @@ func builtInAnyConvertFuncForString(from any, to reflect.Value) error { return nil } -func builtInAnyConvertFuncForFloat64(from any, to reflect.Value) error { - v, err := doFloat64(from) +func (c *Converter) builtInAnyConvertFuncForFloat64(from any, to reflect.Value) error { + v, err := c.Float64(from) if err != nil { return err } @@ -49,8 +49,8 @@ func builtInAnyConvertFuncForFloat64(from any, to reflect.Value) error { return nil } -func builtInAnyConvertFuncForBool(from any, to reflect.Value) error { - v, err := doBool(from) +func (c *Converter) builtInAnyConvertFuncForBool(from any, to reflect.Value) error { + v, err := c.Bool(from) if err != nil { return err } @@ -58,8 +58,8 @@ func builtInAnyConvertFuncForBool(from any, to reflect.Value) error { return nil } -func builtInAnyConvertFuncForBytes(from any, to reflect.Value) error { - v, err := doBytes(from) +func (c *Converter) builtInAnyConvertFuncForBytes(from any, to reflect.Value) error { + v, err := c.Bytes(from) if err != nil { return err } @@ -67,13 +67,20 @@ func builtInAnyConvertFuncForBytes(from any, to reflect.Value) error { return nil } -func builtInAnyConvertFuncForTime(from any, to reflect.Value) error { - *to.Addr().Interface().(*time.Time) = Time(from) +func (c *Converter) builtInAnyConvertFuncForTime(from any, to reflect.Value) error { + t, err := c.Time(from) + if err != nil { + return err + } + *to.Addr().Interface().(*time.Time) = t return nil } -func builtInAnyConvertFuncForGTime(from any, to reflect.Value) error { - v := GTime(from) +func (c *Converter) builtInAnyConvertFuncForGTime(from any, to reflect.Value) error { + v, err := c.GTime(from) + if err != nil { + return err + } if v == nil { v = gtime.New() } diff --git a/util/gconv/gconv_converter_bytes.go b/util/gconv/gconv_converter_bytes.go new file mode 100644 index 000000000..3237bd4bb --- /dev/null +++ b/util/gconv/gconv_converter_bytes.go @@ -0,0 +1,67 @@ +// 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 ( + "math" + "reflect" + + "github.com/gogf/gf/v2/encoding/gbinary" + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/internal/reflection" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +func (c *Converter) Bytes(any any) ([]byte, error) { + if empty.IsNil(any) { + return nil, nil + } + switch value := any.(type) { + case string: + return []byte(value), nil + + case []byte: + return value, nil + + default: + if f, ok := value.(localinterface.IBytes); ok { + return f.Bytes(), nil + } + originValueAndKind := reflection.OriginValueAndKind(any) + switch originValueAndKind.OriginKind { + case reflect.Map: + bytes, err := json.Marshal(any) + if err != nil { + return nil, err + } + return bytes, nil + + case reflect.Array, reflect.Slice: + var ( + ok = true + bytes = make([]byte, originValueAndKind.OriginValue.Len()) + ) + for i := range bytes { + int32Value, err := c.Int32(originValueAndKind.OriginValue.Index(i).Interface()) + if err != nil { + return nil, err + } + if int32Value < 0 || int32Value > math.MaxUint8 { + ok = false + break + } + bytes[i] = byte(int32Value) + } + if ok { + return bytes, nil + } + default: + } + return gbinary.Encode(any), nil + } +} diff --git a/util/gconv/gconv_converter_float.go b/util/gconv/gconv_converter_float.go new file mode 100644 index 000000000..3c186ab56 --- /dev/null +++ b/util/gconv/gconv_converter_float.go @@ -0,0 +1,145 @@ +// 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" + "strconv" + + "github.com/gogf/gf/v2/encoding/gbinary" + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +func (c *Converter) Float32(any any) (float32, error) { + if empty.IsNil(any) { + return 0, nil + } + switch value := any.(type) { + case float32: + return value, nil + case float64: + return float32(value), nil + case []byte: + // TODO: It might panic here for these types. + return gbinary.DecodeToFloat32(value), nil + default: + rv := reflect.ValueOf(any) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return float32(rv.Int()), nil + case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return float32(rv.Uint()), nil + case reflect.Float32, reflect.Float64: + return float32(rv.Float()), nil + case reflect.Bool: + if rv.Bool() { + return 1, nil + } + return 0, nil + case reflect.String: + f, err := strconv.ParseFloat(rv.String(), 32) + if err != nil { + return 0, gerror.WrapCodef( + gcode.CodeInvalidParameter, err, "converting string to float32 failed for: %v", any, + ) + } + return float32(f), nil + case reflect.Ptr: + if rv.IsNil() { + return 0, nil + } + if f, ok := value.(localinterface.IFloat32); ok { + return f.Float32(), nil + } + return c.Float32(rv.Elem().Interface()) + default: + if f, ok := value.(localinterface.IFloat32); ok { + return f.Float32(), nil + } + s, err := c.String(any) + if err != nil { + return 0, err + } + v, err := strconv.ParseFloat(s, 32) + if err != nil { + return 0, gerror.WrapCodef( + gcode.CodeInvalidParameter, err, "converting string to float32 failed for: %v", any, + ) + } + return float32(v), nil + } + } +} + +func (c *Converter) Float64(any any) (float64, error) { + if empty.IsNil(any) { + return 0, nil + } + switch value := any.(type) { + case float32: + return float64(value), nil + case float64: + return value, nil + case []byte: + // TODO: It might panic here for these types. + return gbinary.DecodeToFloat64(value), nil + default: + rv := reflect.ValueOf(any) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return float64(rv.Int()), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return float64(rv.Uint()), nil + case reflect.Uintptr: + return float64(rv.Uint()), nil + case reflect.Float32, reflect.Float64: + // Please Note: + // When the type is float32 or a custom type defined based on float32, + // switching to float64 may result in a few extra decimal places. + return rv.Float(), nil + case reflect.Bool: + if rv.Bool() { + return 1, nil + } + return 0, nil + case reflect.String: + f, err := strconv.ParseFloat(rv.String(), 64) + if err != nil { + return 0, gerror.WrapCodef( + gcode.CodeInvalidParameter, err, "converting string to float64 failed for: %v", any, + ) + } + return f, nil + case reflect.Ptr: + if rv.IsNil() { + return 0, nil + } + if f, ok := value.(localinterface.IFloat64); ok { + return f.Float64(), nil + } + return c.Float64(rv.Elem().Interface()) + default: + if f, ok := value.(localinterface.IFloat64); ok { + return f.Float64(), nil + } + s, err := c.String(any) + if err != nil { + return 0, err + } + v, err := strconv.ParseFloat(s, 64) + if err != nil { + return 0, gerror.WrapCodef( + gcode.CodeInvalidParameter, err, "converting string to float64 failed for: %v", any, + ) + } + return v, nil + } + } +} diff --git a/util/gconv/gconv_converter_int.go b/util/gconv/gconv_converter_int.go new file mode 100644 index 000000000..cc23f314f --- /dev/null +++ b/util/gconv/gconv_converter_int.go @@ -0,0 +1,152 @@ +// 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 ( + "math" + "reflect" + "strconv" + + "github.com/gogf/gf/v2/encoding/gbinary" + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +func (c *Converter) Int(any any) (int, error) { + if v, ok := any.(int); ok { + return v, nil + } + v, err := c.Int64(any) + if err != nil { + return 0, err + } + return int(v), nil +} + +func (c *Converter) Int8(any any) (int8, error) { + if v, ok := any.(int8); ok { + return v, nil + } + v, err := c.Int64(any) + if err != nil { + return 0, err + } + return int8(v), nil +} + +func (c *Converter) Int16(any any) (int16, error) { + if v, ok := any.(int16); ok { + return v, nil + } + v, err := defaultConverter.Int64(any) + if err != nil { + return 0, err + } + return int16(v), nil +} + +func (c *Converter) Int32(any any) (int32, error) { + if v, ok := any.(int32); ok { + return v, nil + } + v, err := c.Int64(any) + if err != nil { + return 0, err + } + return int32(v), nil +} + +func (c *Converter) Int64(any any) (int64, error) { + if empty.IsNil(any) { + return 0, nil + } + if v, ok := any.(int64); ok { + return v, nil + } + rv := reflect.ValueOf(any) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return rv.Int(), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return int64(rv.Uint()), nil + case reflect.Uintptr: + return int64(rv.Uint()), nil + case reflect.Float32, reflect.Float64: + return int64(rv.Float()), nil + case reflect.Bool: + if rv.Bool() { + return 1, nil + } + return 0, nil + case reflect.Ptr: + if rv.IsNil() { + return 0, nil + } + if f, ok := any.(localinterface.IInt64); ok { + return f.Int64(), nil + } + return c.Int64(rv.Elem().Interface()) + case reflect.Slice: + // TODO: It might panic here for these types. + if rv.Type().Elem().Kind() == reflect.Uint8 { + return gbinary.DecodeToInt64(rv.Bytes()), nil + } + case reflect.String: + var ( + s = rv.String() + isMinus = false + ) + if len(s) > 0 { + if s[0] == '-' { + isMinus = true + s = s[1:] + } else if s[0] == '+' { + s = s[1:] + } + } + // Hexadecimal. + if len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') { + if v, e := strconv.ParseInt(s[2:], 16, 64); e == nil { + if isMinus { + return -v, nil + } + return v, nil + } + } + // Decimal. + if v, e := strconv.ParseInt(s, 10, 64); e == nil { + if isMinus { + return -v, nil + } + return v, nil + } + // Float64. + valueInt64, err := c.Float64(s) + if err != nil { + return 0, err + } + if math.IsNaN(valueInt64) { + return 0, nil + } else { + if isMinus { + return -int64(valueInt64), nil + } + return int64(valueInt64), nil + } + default: + if f, ok := any.(localinterface.IInt64); ok { + return f.Int64(), nil + } + } + return 0, gerror.NewCodef( + gcode.CodeInvalidParameter, + `unsupport value type for converting to int64: %v`, + reflect.TypeOf(any), + ) +} diff --git a/util/gconv/gconv_converter_map.go b/util/gconv/gconv_converter_map.go new file mode 100644 index 000000000..13f7a3d22 --- /dev/null +++ b/util/gconv/gconv_converter_map.go @@ -0,0 +1,504 @@ +// 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" + "strings" + + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/internal/utils" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" + "github.com/gogf/gf/v2/util/gtag" +) + +// MapConvert implements the map converting. +// It automatically checks and converts json string to map if `value` is string/[]byte. +// +// TODO completely implement the recursive converting for all types, especially the map. +func (c *Converter) doMapConvert(value interface{}, recursive recursiveType, mustMapReturn bool, option ...MapOption) map[string]interface{} { + if value == nil { + return nil + } + // It redirects to its underlying value if it has implemented interface iVal. + if v, ok := value.(localinterface.IVal); ok { + value = v.Val() + } + var ( + usedOption = getUsedMapOption(option...) + newTags = gtag.StructTagPriority + ) + if usedOption.Deep { + recursive = recursiveTypeTrue + } + switch len(usedOption.Tags) { + case 0: + // No need handling. + case 1: + newTags = append(strings.Split(usedOption.Tags[0], ","), gtag.StructTagPriority...) + default: + newTags = append(usedOption.Tags, gtag.StructTagPriority...) + } + // Assert the common combination of types, and finally it uses reflection. + dataMap := make(map[string]interface{}) + switch r := value.(type) { + case string: + // If it is a JSON string, automatically unmarshal it! + if len(r) > 0 && r[0] == '{' && r[len(r)-1] == '}' { + if err := json.UnmarshalUseNumber([]byte(r), &dataMap); err != nil { + return nil + } + } else { + return nil + } + case []byte: + // If it is a JSON string, automatically unmarshal it! + if len(r) > 0 && r[0] == '{' && r[len(r)-1] == '}' { + if err := json.UnmarshalUseNumber(r, &dataMap); err != nil { + return nil + } + } else { + return nil + } + case map[interface{}]interface{}: + recursiveOption := usedOption + recursiveOption.Tags = newTags + for k, v := range r { + dataMap[String(k)] = c.doMapConvertForMapOrStructValue( + doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: v, + RecursiveType: recursive, + RecursiveOption: recursive == recursiveTypeTrue, + Option: recursiveOption, + }, + ) + } + case map[interface{}]string: + for k, v := range r { + dataMap[String(k)] = v + } + case map[interface{}]int: + for k, v := range r { + dataMap[String(k)] = v + } + case map[interface{}]uint: + for k, v := range r { + dataMap[String(k)] = v + } + case map[interface{}]float32: + for k, v := range r { + dataMap[String(k)] = v + } + case map[interface{}]float64: + for k, v := range r { + dataMap[String(k)] = v + } + case map[string]bool: + for k, v := range r { + dataMap[k] = v + } + case map[string]int: + for k, v := range r { + dataMap[k] = v + } + case map[string]uint: + for k, v := range r { + dataMap[k] = v + } + case map[string]float32: + for k, v := range r { + dataMap[k] = v + } + case map[string]float64: + for k, v := range r { + dataMap[k] = v + } + case map[string]string: + for k, v := range r { + dataMap[k] = v + } + case map[string]interface{}: + if recursive == recursiveTypeTrue { + recursiveOption := usedOption + recursiveOption.Tags = newTags + // A copy of current map. + for k, v := range r { + dataMap[k] = c.doMapConvertForMapOrStructValue( + doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: v, + RecursiveType: recursive, + RecursiveOption: recursive == recursiveTypeTrue, + Option: recursiveOption, + }, + ) + } + } else { + // It returns the map directly without any changing. + return r + } + case map[int]interface{}: + recursiveOption := usedOption + recursiveOption.Tags = newTags + for k, v := range r { + dataMap[String(k)] = c.doMapConvertForMapOrStructValue( + doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: v, + RecursiveType: recursive, + RecursiveOption: recursive == recursiveTypeTrue, + Option: recursiveOption, + }, + ) + } + case map[int]string: + for k, v := range r { + dataMap[String(k)] = v + } + case map[uint]string: + for k, v := range r { + dataMap[String(k)] = v + } + + default: + // Not a common type, it then uses reflection for conversion. + var reflectValue reflect.Value + if v, ok := value.(reflect.Value); ok { + reflectValue = v + } else { + reflectValue = reflect.ValueOf(value) + } + reflectKind := reflectValue.Kind() + // If it is a pointer, we should find its real data type. + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { + // If `value` is type of array, it converts the value of even number index as its key and + // the value of odd number index as its corresponding value, for example: + // []string{"k1","v1","k2","v2"} => map[string]interface{}{"k1":"v1", "k2":"v2"} + // []string{"k1","v1","k2"} => map[string]interface{}{"k1":"v1", "k2":nil} + case reflect.Slice, reflect.Array: + length := reflectValue.Len() + for i := 0; i < length; i += 2 { + if i+1 < length { + dataMap[String(reflectValue.Index(i).Interface())] = reflectValue.Index(i + 1).Interface() + } else { + dataMap[String(reflectValue.Index(i).Interface())] = nil + } + } + case reflect.Map, reflect.Struct, reflect.Interface: + recursiveOption := usedOption + recursiveOption.Tags = newTags + convertedValue := c.doMapConvertForMapOrStructValue( + doMapConvertForMapOrStructValueInput{ + IsRoot: true, + Value: value, + RecursiveType: recursive, + RecursiveOption: recursive == recursiveTypeTrue, + Option: recursiveOption, + MustMapReturn: mustMapReturn, + }, + ) + if m, ok := convertedValue.(map[string]interface{}); ok { + return m + } + return nil + default: + return nil + } + } + return dataMap +} + +func getUsedMapOption(option ...MapOption) MapOption { + var usedOption MapOption + if len(option) > 0 { + usedOption = option[0] + } + return usedOption +} + +type doMapConvertForMapOrStructValueInput struct { + IsRoot bool // It returns directly if it is not root and with no recursive converting. + Value interface{} // Current operation value. + RecursiveType recursiveType // The type from top function entry. + RecursiveOption bool // Whether convert recursively for `current` operation. + Option MapOption // Map converting option. + MustMapReturn bool // Must return map instead of Value when empty. +} + +func (c *Converter) doMapConvertForMapOrStructValue(in doMapConvertForMapOrStructValueInput) interface{} { + if !in.IsRoot && !in.RecursiveOption { + return in.Value + } + + var reflectValue reflect.Value + if v, ok := in.Value.(reflect.Value); ok { + reflectValue = v + in.Value = v.Interface() + } else { + reflectValue = reflect.ValueOf(in.Value) + } + reflectKind := reflectValue.Kind() + // If it is a pointer, we should find its real data type. + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { + case reflect.Map: + var ( + mapIter = reflectValue.MapRange() + dataMap = make(map[string]interface{}) + ) + for mapIter.Next() { + var ( + mapKeyValue = mapIter.Value() + mapValue interface{} + ) + switch { + case mapKeyValue.IsZero(): + if utils.CanCallIsNil(mapKeyValue) && mapKeyValue.IsNil() { + // quick check for nil value. + mapValue = nil + } else { + // in case of: + // exception recovered: reflect: call of reflect.Value.Interface on zero Value + mapValue = reflect.New(mapKeyValue.Type()).Elem().Interface() + } + default: + mapValue = mapKeyValue.Interface() + } + dataMap[String(mapIter.Key().Interface())] = c.doMapConvertForMapOrStructValue( + doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: mapValue, + RecursiveType: in.RecursiveType, + RecursiveOption: in.RecursiveType == recursiveTypeTrue, + Option: in.Option, + }, + ) + } + return dataMap + + case reflect.Struct: + var dataMap = make(map[string]interface{}) + // Map converting interface check. + if v, ok := in.Value.(localinterface.IMapStrAny); ok { + // Value copy, in case of concurrent safety. + for mapK, mapV := range v.MapStrAny() { + if in.RecursiveOption { + dataMap[mapK] = c.doMapConvertForMapOrStructValue( + doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: mapV, + RecursiveType: in.RecursiveType, + RecursiveOption: in.RecursiveType == recursiveTypeTrue, + Option: in.Option, + }, + ) + } else { + dataMap[mapK] = mapV + } + } + if len(dataMap) > 0 { + return dataMap + } + } + // Using reflect for converting. + var ( + rtField reflect.StructField + rvField reflect.Value + reflectType = reflectValue.Type() // attribute value type. + mapKey = "" // mapKey may be the tag name or the struct attribute name. + ) + for i := 0; i < reflectValue.NumField(); i++ { + rtField = reflectType.Field(i) + rvField = reflectValue.Field(i) + // Only convert the public attributes. + fieldName := rtField.Name + if !utils.IsLetterUpper(fieldName[0]) { + continue + } + mapKey = "" + fieldTag := rtField.Tag + for _, tag := range in.Option.Tags { + if mapKey = fieldTag.Get(tag); mapKey != "" { + break + } + } + if mapKey == "" { + mapKey = fieldName + } else { + // Support json tag feature: -, omitempty + mapKey = strings.TrimSpace(mapKey) + if mapKey == "-" { + continue + } + array := strings.Split(mapKey, ",") + if len(array) > 1 { + switch strings.TrimSpace(array[1]) { + case "omitempty": + if in.Option.OmitEmpty && empty.IsEmpty(rvField.Interface()) { + continue + } else { + mapKey = strings.TrimSpace(array[0]) + } + default: + mapKey = strings.TrimSpace(array[0]) + } + } + if mapKey == "" { + mapKey = fieldName + } + } + if in.RecursiveOption || rtField.Anonymous { + // Do map converting recursively. + var ( + rvAttrField = rvField + rvAttrKind = rvField.Kind() + ) + if rvAttrKind == reflect.Ptr { + rvAttrField = rvField.Elem() + rvAttrKind = rvAttrField.Kind() + } + switch rvAttrKind { + case reflect.Struct: + // Embedded struct and has no fields, just ignores it. + // Eg: gmeta.Meta + if rvAttrField.Type().NumField() == 0 { + continue + } + var ( + hasNoTag = mapKey == fieldName + // DO NOT use rvAttrField.Interface() here, + // as it might be changed from pointer to struct. + rvInterface = rvField.Interface() + ) + switch { + case hasNoTag && rtField.Anonymous: + // It means this attribute field has no tag. + // Overwrite the attribute with sub-struct attribute fields. + anonymousValue := c.doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: rvInterface, + RecursiveType: in.RecursiveType, + RecursiveOption: true, + Option: in.Option, + }) + if m, ok := anonymousValue.(map[string]interface{}); ok { + for k, v := range m { + dataMap[k] = v + } + } else { + dataMap[mapKey] = rvInterface + } + + // It means this attribute field has desired tag. + case !hasNoTag && rtField.Anonymous: + dataMap[mapKey] = c.doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: rvInterface, + RecursiveType: in.RecursiveType, + RecursiveOption: true, + Option: in.Option, + }) + + default: + dataMap[mapKey] = c.doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: rvInterface, + RecursiveType: in.RecursiveType, + RecursiveOption: in.RecursiveType == recursiveTypeTrue, + Option: in.Option, + }) + } + + // The struct attribute is type of slice. + case reflect.Array, reflect.Slice: + length := rvAttrField.Len() + if length == 0 { + dataMap[mapKey] = rvAttrField.Interface() + break + } + array := make([]interface{}, length) + for arrayIndex := 0; arrayIndex < length; arrayIndex++ { + array[arrayIndex] = c.doMapConvertForMapOrStructValue( + doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: rvAttrField.Index(arrayIndex).Interface(), + RecursiveType: in.RecursiveType, + RecursiveOption: in.RecursiveType == recursiveTypeTrue, + Option: in.Option, + }, + ) + } + dataMap[mapKey] = array + case reflect.Map: + var ( + mapIter = rvAttrField.MapRange() + nestedMap = make(map[string]interface{}) + ) + for mapIter.Next() { + nestedMap[String(mapIter.Key().Interface())] = c.doMapConvertForMapOrStructValue( + doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: mapIter.Value().Interface(), + RecursiveType: in.RecursiveType, + RecursiveOption: in.RecursiveType == recursiveTypeTrue, + Option: in.Option, + }, + ) + } + dataMap[mapKey] = nestedMap + default: + if rvField.IsValid() { + dataMap[mapKey] = reflectValue.Field(i).Interface() + } else { + dataMap[mapKey] = nil + } + } + } else { + // No recursive map value converting + if rvField.IsValid() { + dataMap[mapKey] = reflectValue.Field(i).Interface() + } else { + dataMap[mapKey] = nil + } + } + } + if !in.MustMapReturn && len(dataMap) == 0 { + return in.Value + } + return dataMap + + // The given value is type of slice. + case reflect.Array, reflect.Slice: + length := reflectValue.Len() + if length == 0 { + break + } + array := make([]interface{}, reflectValue.Len()) + for i := 0; i < length; i++ { + array[i] = c.doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: reflectValue.Index(i).Interface(), + RecursiveType: in.RecursiveType, + RecursiveOption: in.RecursiveType == recursiveTypeTrue, + Option: in.Option, + }) + } + return array + + default: + } + return in.Value +} diff --git a/util/gconv/gconv_converter_maps.go b/util/gconv/gconv_converter_maps.go new file mode 100644 index 000000000..a5c4126a5 --- /dev/null +++ b/util/gconv/gconv_converter_maps.go @@ -0,0 +1,7 @@ +// 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 diff --git a/util/gconv/gconv_converter_maptomap.go b/util/gconv/gconv_converter_maptomap.go new file mode 100644 index 000000000..46a6bf879 --- /dev/null +++ b/util/gconv/gconv_converter_maptomap.go @@ -0,0 +1,126 @@ +// 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" +) + +// MapToMap converts any map type variable `params` to another map type variable `pointer`. +// +// The parameter `params` can be any type of map, like: +// map[string]string, map[string]struct, map[string]*struct, reflect.Value, etc. +// +// The parameter `pointer` should be type of *map, like: +// map[int]string, map[string]struct, map[string]*struct, reflect.Value, etc. +// +// 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 (c *Converter) MapToMap(params any, pointer any, mapping ...map[string]string) (err error) { + var ( + paramsRv reflect.Value + paramsKind reflect.Kind + keyToAttributeNameMapping map[string]string + ) + if len(mapping) > 0 { + keyToAttributeNameMapping = mapping[0] + } + if v, ok := params.(reflect.Value); ok { + paramsRv = v + } else { + paramsRv = reflect.ValueOf(params) + } + paramsKind = paramsRv.Kind() + if paramsKind == reflect.Ptr { + paramsRv = paramsRv.Elem() + paramsKind = paramsRv.Kind() + } + if paramsKind != reflect.Map { + return c.MapToMap(Map(params), pointer, mapping...) + } + // Empty params map, no need continue. + if paramsRv.Len() == 0 { + return nil + } + var pointerRv reflect.Value + if v, ok := pointer.(reflect.Value); ok { + pointerRv = v + } else { + pointerRv = reflect.ValueOf(pointer) + } + pointerKind := pointerRv.Kind() + for pointerKind == reflect.Ptr { + pointerRv = pointerRv.Elem() + pointerKind = pointerRv.Kind() + } + if pointerKind != reflect.Map { + return gerror.NewCodef( + gcode.CodeInvalidParameter, + `destination pointer should be type of *map, but got: %s`, + pointerKind, + ) + } + defer func() { + // Catch the panic, especially the reflection operation panics. + if exception := recover(); exception != nil { + if v, ok := exception.(error); ok && gerror.HasStack(v) { + err = v + } else { + err = gerror.NewCodeSkipf(gcode.CodeInternalPanic, 1, "%+v", exception) + } + } + }() + var ( + paramsKeys = paramsRv.MapKeys() + pointerKeyType = pointerRv.Type().Key() + pointerValueType = pointerRv.Type().Elem() + pointerValueKind = pointerValueType.Kind() + dataMap = reflect.MakeMapWithSize(pointerRv.Type(), len(paramsKeys)) + ) + // Retrieve the true element type of target map. + if pointerValueKind == reflect.Ptr { + pointerValueKind = pointerValueType.Elem().Kind() + } + for _, key := range paramsKeys { + mapValue := reflect.New(pointerValueType).Elem() + switch pointerValueKind { + case reflect.Map, reflect.Struct: + if err = c.Struct( + paramsRv.MapIndex(key).Interface(), mapValue, keyToAttributeNameMapping, "", + ); err != nil { + return err + } + default: + mapValue.Set( + reflect.ValueOf( + c.doConvert(doConvertInput{ + FromValue: paramsRv.MapIndex(key).Interface(), + ToTypeName: pointerValueType.String(), + ReferValue: mapValue, + Extra: nil, + }), + ), + ) + } + var mapKey = reflect.ValueOf( + c.doConvert( + doConvertInput{ + FromValue: key.Interface(), + ToTypeName: pointerKeyType.Name(), + ReferValue: reflect.New(pointerKeyType).Elem().Interface(), + Extra: nil, + }, + ), + ) + dataMap.SetMapIndex(mapKey, mapValue) + } + pointerRv.Set(dataMap) + return nil +} diff --git a/util/gconv/gconv_converter_maptomaps.go b/util/gconv/gconv_converter_maptomaps.go new file mode 100644 index 000000000..2c33ebfdc --- /dev/null +++ b/util/gconv/gconv_converter_maptomaps.go @@ -0,0 +1,120 @@ +// 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" +) + +// MapToMaps converts any map type variable `params` to another map slice variable `pointer`. +// +// The parameter `params` can be type of []map, []*map, []struct, []*struct. +// +// The parameter `pointer` should be type of []map, []*map. +// +// The optional parameter `mapping` is used for struct attribute to map key mapping, which makes +// sense only if the item of `params` is type struct. +func (c *Converter) MapToMaps(params any, pointer any, paramKeyToAttrMap ...map[string]string) (err error) { + // Params and its element type check. + var ( + paramsRv reflect.Value + paramsKind reflect.Kind + ) + if v, ok := params.(reflect.Value); ok { + paramsRv = v + } else { + paramsRv = reflect.ValueOf(params) + } + paramsKind = paramsRv.Kind() + if paramsKind == reflect.Ptr { + paramsRv = paramsRv.Elem() + paramsKind = paramsRv.Kind() + } + if paramsKind != reflect.Array && paramsKind != reflect.Slice { + return gerror.NewCode( + gcode.CodeInvalidParameter, + "params should be type of slice, example: []map/[]*map/[]struct/[]*struct", + ) + } + var ( + paramsElem = paramsRv.Type().Elem() + paramsElemKind = paramsElem.Kind() + ) + if paramsElemKind == reflect.Ptr { + paramsElem = paramsElem.Elem() + paramsElemKind = paramsElem.Kind() + } + if paramsElemKind != reflect.Map && + paramsElemKind != reflect.Struct && + paramsElemKind != reflect.Interface { + return gerror.NewCodef( + gcode.CodeInvalidParameter, + "params element should be type of map/*map/struct/*struct, but got: %s", + paramsElemKind, + ) + } + // Empty slice, no need continue. + if paramsRv.Len() == 0 { + return nil + } + // Pointer and its element type check. + var ( + pointerRv = reflect.ValueOf(pointer) + pointerKind = pointerRv.Kind() + ) + for pointerKind == reflect.Ptr { + pointerRv = pointerRv.Elem() + pointerKind = pointerRv.Kind() + } + if pointerKind != reflect.Array && pointerKind != reflect.Slice { + return gerror.NewCode(gcode.CodeInvalidParameter, "pointer should be type of *[]map/*[]*map") + } + var ( + pointerElemType = pointerRv.Type().Elem() + pointerElemKind = pointerElemType.Kind() + ) + if pointerElemKind == reflect.Ptr { + pointerElemKind = pointerElemType.Elem().Kind() + } + if pointerElemKind != reflect.Map { + return gerror.NewCode(gcode.CodeInvalidParameter, "pointer element should be type of map/*map") + } + defer func() { + // Catch the panic, especially the reflection operation panics. + if exception := recover(); exception != nil { + if v, ok := exception.(error); ok && gerror.HasStack(v) { + err = v + } else { + err = gerror.NewCodeSkipf(gcode.CodeInternalPanic, 1, "%+v", exception) + } + } + }() + var ( + pointerSlice = reflect.MakeSlice(pointerRv.Type(), paramsRv.Len(), paramsRv.Len()) + ) + for i := 0; i < paramsRv.Len(); i++ { + var item reflect.Value + if pointerElemType.Kind() == reflect.Ptr { + item = reflect.New(pointerElemType.Elem()) + if err = MapToMap(paramsRv.Index(i).Interface(), item, paramKeyToAttrMap...); err != nil { + return err + } + pointerSlice.Index(i).Set(item) + } else { + item = reflect.New(pointerElemType) + if err = MapToMap(paramsRv.Index(i).Interface(), item, paramKeyToAttrMap...); err != nil { + return err + } + pointerSlice.Index(i).Set(item.Elem()) + } + } + pointerRv.Set(pointerSlice) + return +} diff --git a/util/gconv/gconv_converter_rune.go b/util/gconv/gconv_converter_rune.go new file mode 100644 index 000000000..b5bff899e --- /dev/null +++ b/util/gconv/gconv_converter_rune.go @@ -0,0 +1,29 @@ +// 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 + +func (c *Converter) Rune(any any) (rune, error) { + if v, ok := any.(rune); ok { + return v, nil + } + v, err := c.Int32(any) + if err != nil { + return 0, err + } + return v, nil +} + +func (c *Converter) Runes(any any) ([]rune, error) { + if v, ok := any.([]rune); ok { + return v, nil + } + s, err := c.String(any) + if err != nil { + return nil, err + } + return []rune(s), nil +} diff --git a/util/gconv/gconv_converter_scan.go b/util/gconv/gconv_converter_scan.go new file mode 100644 index 000000000..269b3de94 --- /dev/null +++ b/util/gconv/gconv_converter_scan.go @@ -0,0 +1,336 @@ +// 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" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +func (c *Converter) Scan(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 + } + // Check if dstPointer is nil, which is an invalid parameter + if dstPointer == nil { + return gerror.NewCode( + gcode.CodeInvalidParameter, + `destination pointer should not be nil`, + ) + } + + // Get the reflection type and value of dstPointer + var ( + dstPointerReflectType reflect.Type + dstPointerReflectValue reflect.Value + ) + if v, ok := dstPointer.(reflect.Value); ok { + dstPointerReflectValue = v + dstPointerReflectType = v.Type() + } else { + dstPointerReflectValue = reflect.ValueOf(dstPointer) + // Do not use dstPointerReflectValue.Type() as dstPointerReflectValue might be zero + dstPointerReflectType = reflect.TypeOf(dstPointer) + } + + // Validate the kind of dstPointer + var dstPointerReflectKind = dstPointerReflectType.Kind() + if dstPointerReflectKind != reflect.Ptr { + // If dstPointer is not a pointer, try to get its address + if dstPointerReflectValue.CanAddr() { + dstPointerReflectValue = dstPointerReflectValue.Addr() + dstPointerReflectType = dstPointerReflectValue.Type() + dstPointerReflectKind = dstPointerReflectType.Kind() + } else { + // If dstPointer is not a pointer and cannot be addressed, return an error + return gerror.NewCodef( + gcode.CodeInvalidParameter, + `destination pointer should be type of pointer, but got type: %v`, + dstPointerReflectType, + ) + } + } + + // Get the reflection value of srcValue + var srcValueReflectValue reflect.Value + if v, ok := srcValue.(reflect.Value); ok { + srcValueReflectValue = v + } else { + srcValueReflectValue = reflect.ValueOf(srcValue) + } + + // Get the element type and kind of dstPointer + var ( + dstPointerReflectValueElem = dstPointerReflectValue.Elem() + dstPointerReflectValueElemKind = dstPointerReflectValueElem.Kind() + ) + // Handle multiple level pointers + if dstPointerReflectValueElemKind == reflect.Ptr { + if dstPointerReflectValueElem.IsNil() { + // Create a new value for the pointer dereference + nextLevelPtr := reflect.New(dstPointerReflectValueElem.Type().Elem()) + // Recursively scan into the dereferenced pointer + if err = Scan(srcValueReflectValue, nextLevelPtr, paramKeyToAttrMap...); err == nil { + dstPointerReflectValueElem.Set(nextLevelPtr) + } + return + } + return Scan(srcValueReflectValue, dstPointerReflectValueElem, paramKeyToAttrMap...) + } + + // Check if srcValue and dstPointer are the same type, in which case direct assignment can be performed + if ok := doConvertWithTypeCheck(srcValueReflectValue, dstPointerReflectValueElem); ok { + return nil + } + + // Handle different destination types + switch dstPointerReflectValueElemKind { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + // Convert to int type + dstPointerReflectValueElem.SetInt(Int64(srcValue)) + return nil + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + // Convert to uint type + dstPointerReflectValueElem.SetUint(Uint64(srcValue)) + return nil + + case reflect.Float32, reflect.Float64: + // Convert to float type + dstPointerReflectValueElem.SetFloat(Float64(srcValue)) + return nil + + case reflect.String: + // Convert to string type + dstPointerReflectValueElem.SetString(String(srcValue)) + return nil + + case reflect.Bool: + // Convert to bool type + dstPointerReflectValueElem.SetBool(Bool(srcValue)) + return nil + + case reflect.Slice: + // Handle slice type conversion + var ( + dstElemType = dstPointerReflectValueElem.Type().Elem() + dstElemKind = dstElemType.Kind() + ) + // The slice element might be a pointer type + if dstElemKind == reflect.Ptr { + dstElemType = dstElemType.Elem() + dstElemKind = dstElemType.Kind() + } + // Special handling for struct or map slice elements + if dstElemKind == reflect.Struct || dstElemKind == reflect.Map { + return c.doScanForComplicatedTypes(srcValue, dstPointer, dstPointerReflectType, paramKeyToAttrMap...) + } + // Handle basic type slice conversions + var srcValueReflectValueKind = srcValueReflectValue.Kind() + if srcValueReflectValueKind == reflect.Slice || srcValueReflectValueKind == reflect.Array { + var ( + srcLen = srcValueReflectValue.Len() + newSlice = reflect.MakeSlice(dstPointerReflectValueElem.Type(), srcLen, srcLen) + ) + for i := 0; i < srcLen; i++ { + srcElem := srcValueReflectValue.Index(i).Interface() + switch dstElemType.Kind() { + case reflect.String: + newSlice.Index(i).SetString(String(srcElem)) + case reflect.Int: + newSlice.Index(i).SetInt(Int64(srcElem)) + case reflect.Int64: + newSlice.Index(i).SetInt(Int64(srcElem)) + case reflect.Float64: + newSlice.Index(i).SetFloat(Float64(srcElem)) + case reflect.Bool: + newSlice.Index(i).SetBool(Bool(srcElem)) + default: + return Scan( + srcElem, newSlice.Index(i).Addr().Interface(), paramKeyToAttrMap..., + ) + } + } + dstPointerReflectValueElem.Set(newSlice) + return nil + } + return c.doScanForComplicatedTypes(srcValue, dstPointer, dstPointerReflectType, paramKeyToAttrMap...) + + default: + // Handle complex types (structs, maps, etc.) + return c.doScanForComplicatedTypes(srcValue, dstPointer, dstPointerReflectType, paramKeyToAttrMap...) + } +} + +// doScanForComplicatedTypes handles the scanning of complex data types. +// It supports converting between maps, structs, and slices of these types. +// The function first attempts JSON conversion, then falls back to specific type handling. +// +// It supports `pointer` in type of `*map/*[]map/*[]*map/*struct/**struct/*[]struct/*[]*struct` for converting. +// +// Parameters: +// - srcValue: The source value to convert from +// - dstPointer: The destination pointer to convert to +// - dstPointerReflectType: The reflection type of the destination pointer +// - paramKeyToAttrMap: Optional mapping between parameter keys and struct attribute names +func (c *Converter) doScanForComplicatedTypes( + srcValue, dstPointer any, + dstPointerReflectType reflect.Type, + paramKeyToAttrMap ...map[string]string, +) error { + // Try JSON conversion first + ok, err := doConvertWithJsonCheck(srcValue, dstPointer) + if err != nil { + return err + } + if ok { + return nil + } + + // Handle specific type conversions + var ( + dstPointerReflectTypeElem = dstPointerReflectType.Elem() + dstPointerReflectTypeElemKind = dstPointerReflectTypeElem.Kind() + keyToAttributeNameMapping map[string]string + ) + if len(paramKeyToAttrMap) > 0 { + keyToAttributeNameMapping = paramKeyToAttrMap[0] + } + + // Handle different destination types + switch dstPointerReflectTypeElemKind { + case reflect.Map: + // Convert map to map + return c.MapToMap(srcValue, dstPointer, paramKeyToAttrMap...) + + case reflect.Array, reflect.Slice: + var ( + sliceElem = dstPointerReflectTypeElem.Elem() + sliceElemKind = sliceElem.Kind() + ) + // Handle pointer elements + for sliceElemKind == reflect.Ptr { + sliceElem = sliceElem.Elem() + sliceElemKind = sliceElem.Kind() + } + if sliceElemKind == reflect.Map { + // Convert to slice of maps + return c.MapToMaps(srcValue, dstPointer, paramKeyToAttrMap...) + } + // Convert to slice of structs + return c.Structs(srcValue, dstPointer, keyToAttributeNameMapping, "") + + default: + // Convert to single struct + return c.Struct(srcValue, dstPointer, keyToAttributeNameMapping, "") + } +} + +// doConvertWithTypeCheck supports `pointer` in type of `*map/*[]map/*[]*map/*struct/**struct/*[]struct/*[]*struct` +// for converting. +func doConvertWithTypeCheck(srcValueReflectValue, dstPointerReflectValueElem reflect.Value) (ok bool) { + if !dstPointerReflectValueElem.IsValid() || !srcValueReflectValue.IsValid() { + return false + } + switch { + // Examples: + // UploadFile => UploadFile + // []UploadFile => []UploadFile + // *UploadFile => *UploadFile + // *[]UploadFile => *[]UploadFile + // map[int][int] => map[int][int] + // []map[int][int] => []map[int][int] + // *[]map[int][int] => *[]map[int][int] + case dstPointerReflectValueElem.Type() == srcValueReflectValue.Type(): + dstPointerReflectValueElem.Set(srcValueReflectValue) + return true + + // Examples: + // UploadFile => *UploadFile + // []UploadFile => *[]UploadFile + // map[int][int] => *map[int][int] + // []map[int][int] => *[]map[int][int] + case dstPointerReflectValueElem.Kind() == reflect.Ptr && + dstPointerReflectValueElem.Elem().IsValid() && + dstPointerReflectValueElem.Elem().Type() == srcValueReflectValue.Type(): + dstPointerReflectValueElem.Elem().Set(srcValueReflectValue) + return true + + // Examples: + // *UploadFile => UploadFile + // *[]UploadFile => []UploadFile + // *map[int][int] => map[int][int] + // *[]map[int][int] => []map[int][int] + case srcValueReflectValue.Kind() == reflect.Ptr && + srcValueReflectValue.Elem().IsValid() && + dstPointerReflectValueElem.Type() == srcValueReflectValue.Elem().Type(): + dstPointerReflectValueElem.Set(srcValueReflectValue.Elem()) + return true + + default: + return false + } +} + +// doConvertWithJsonCheck attempts to convert the source value to the destination +// using JSON marshaling and unmarshaling. This is particularly useful for complex +// types that can be represented as JSON. +// +// Parameters: +// - srcValue: The source value to convert from +// - dstPointer: The destination pointer to convert to +// +// Returns: +// - bool: true if JSON conversion was successful +// - error: any error that occurred during conversion +func doConvertWithJsonCheck(srcValue any, dstPointer any) (ok bool, err error) { + switch valueResult := srcValue.(type) { + case []byte: + if json.Valid(valueResult) { + if dstPointerReflectType, ok := dstPointer.(reflect.Value); ok { + if dstPointerReflectType.Kind() == reflect.Ptr { + if dstPointerReflectType.IsNil() { + return false, nil + } + return true, json.UnmarshalUseNumber(valueResult, dstPointerReflectType.Interface()) + } else if dstPointerReflectType.CanAddr() { + return true, json.UnmarshalUseNumber(valueResult, dstPointerReflectType.Addr().Interface()) + } + } else { + return true, json.UnmarshalUseNumber(valueResult, dstPointer) + } + } + + case string: + if valueBytes := []byte(valueResult); json.Valid(valueBytes) { + if dstPointerReflectType, ok := dstPointer.(reflect.Value); ok { + if dstPointerReflectType.Kind() == reflect.Ptr { + if dstPointerReflectType.IsNil() { + return false, nil + } + return true, json.UnmarshalUseNumber(valueBytes, dstPointerReflectType.Interface()) + } else if dstPointerReflectType.CanAddr() { + return true, json.UnmarshalUseNumber(valueBytes, dstPointerReflectType.Addr().Interface()) + } + } else { + return true, json.UnmarshalUseNumber(valueBytes, dstPointer) + } + } + + default: + // The `params` might be struct that implements interface function Interface, eg: gvar.Var. + if v, ok := srcValue.(localinterface.IInterface); ok { + return doConvertWithJsonCheck(v.Interface(), dstPointer) + } + } + return false, nil +} diff --git a/util/gconv/gconv_converter_string.go b/util/gconv/gconv_converter_string.go new file mode 100644 index 000000000..07e60247b --- /dev/null +++ b/util/gconv/gconv_converter_string.go @@ -0,0 +1,135 @@ +// 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 ( + "fmt" + "reflect" + "strconv" + "time" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +func (c *Converter) String(any any) (string, error) { + if empty.IsNil(any) { + return "", nil + } + switch value := any.(type) { + case int: + return strconv.Itoa(value), nil + case int8: + return strconv.Itoa(int(value)), nil + case int16: + return strconv.Itoa(int(value)), nil + case int32: + return strconv.Itoa(int(value)), nil + case int64: + return strconv.FormatInt(value, 10), nil + case uint: + return strconv.FormatUint(uint64(value), 10), nil + case uint8: + return strconv.FormatUint(uint64(value), 10), nil + case uint16: + return strconv.FormatUint(uint64(value), 10), nil + case uint32: + return strconv.FormatUint(uint64(value), 10), nil + case uint64: + return strconv.FormatUint(value, 10), nil + case float32: + return strconv.FormatFloat(float64(value), 'f', -1, 32), nil + case float64: + return strconv.FormatFloat(value, 'f', -1, 64), nil + case bool: + return strconv.FormatBool(value), nil + case string: + return value, nil + case []byte: + return string(value), nil + case complex64, complex128: + return fmt.Sprintf("%v", value), nil + case time.Time: + if value.IsZero() { + return "", nil + } + return value.String(), nil + case *time.Time: + if value == nil { + return "", nil + } + return value.String(), nil + case gtime.Time: + if value.IsZero() { + return "", nil + } + return value.String(), nil + case *gtime.Time: + if value == nil { + return "", nil + } + return value.String(), nil + default: + if f, ok := value.(localinterface.IString); ok { + // If the variable implements the String() interface, + // then use that interface to perform the conversion + return f.String(), nil + } + if f, ok := value.(localinterface.IError); ok { + // If the variable implements the Error() interface, + // then use that interface to perform the conversion + return f.Error(), nil + } + // Reflect checks. + var ( + rv = reflect.ValueOf(value) + kind = rv.Kind() + ) + switch kind { + case + reflect.Chan, + reflect.Map, + reflect.Slice, + reflect.Func, + reflect.Interface, + reflect.UnsafePointer: + if rv.IsNil() { + return "", nil + } + case reflect.String: + return rv.String(), nil + case reflect.Ptr: + if rv.IsNil() { + return "", nil + } + return c.String(rv.Elem().Interface()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.FormatInt(rv.Int(), 10), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return strconv.FormatUint(rv.Uint(), 10), nil + case reflect.Uintptr: + return strconv.FormatUint(rv.Uint(), 10), nil + case reflect.Float32, reflect.Float64: + return strconv.FormatFloat(rv.Float(), 'f', -1, 64), nil + case reflect.Bool: + return strconv.FormatBool(rv.Bool()), nil + default: + } + // Finally, we use json.Marshal to convert. + jsonContent, err := json.Marshal(value) + if err != nil { + return fmt.Sprint(value), gerror.WrapCodef( + gcode.CodeInvalidParameter, err, "error marshaling value to JSON for: %v", value, + ) + } + return string(jsonContent), nil + } +} diff --git a/util/gconv/gconv_converter_struct.go b/util/gconv/gconv_converter_struct.go new file mode 100644 index 000000000..74f21c0bf --- /dev/null +++ b/util/gconv/gconv_converter_struct.go @@ -0,0 +1,628 @@ +// 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" + "strings" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/internal/utils" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" + "github.com/gogf/gf/v2/util/gconv/internal/structcache" +) + +// Struct is the core internal converting function for any data to struct. +func (c *Converter) Struct( + params any, + pointer any, + paramKeyToAttrMap map[string]string, + priorityTag string, +) (err error) { + if params == nil { + // If `params` is nil, no conversion. + return nil + } + if pointer == nil { + return gerror.NewCode(gcode.CodeInvalidParameter, "object pointer cannot be nil") + } + + // JSON content converting. + ok, err := doConvertWithJsonCheck(params, pointer) + if err != nil { + return err + } + if ok { + return nil + } + + defer func() { + // Catch the panic, especially the reflection operation panics. + if exception := recover(); exception != nil { + if v, ok := exception.(error); ok && gerror.HasStack(v) { + err = v + } else { + err = gerror.NewCodeSkipf(gcode.CodeInternalPanic, 1, "%+v", exception) + } + } + }() + + var ( + paramsReflectValue 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. + ) + if v, ok := params.(reflect.Value); ok { + paramsReflectValue = v + } else { + paramsReflectValue = reflect.ValueOf(params) + } + paramsInterface = paramsReflectValue.Interface() + if v, ok := pointer.(reflect.Value); ok { + pointerReflectValue = v + pointerElemReflectValue = v + } else { + pointerReflectValue = reflect.ValueOf(pointer) + pointerReflectKind = pointerReflectValue.Kind() + if pointerReflectKind != reflect.Ptr { + return gerror.NewCodef( + gcode.CodeInvalidParameter, + "destination pointer should be type of '*struct', but got '%v'", + pointerReflectKind, + ) + } + // Using IsNil on reflect.Ptr variable is OK. + if !pointerReflectValue.IsValid() || pointerReflectValue.IsNil() { + return gerror.NewCode( + gcode.CodeInvalidParameter, + "destination pointer cannot be nil", + ) + } + pointerElemReflectValue = pointerReflectValue.Elem() + } + + // If `params` and `pointer` are the same type, the do directly assignment. + // For performance enhancement purpose. + if ok = doConvertWithTypeCheck(paramsReflectValue, pointerElemReflectValue); ok { + return nil + } + + // custom convert. + if ok, err = c.callCustomConverter(paramsReflectValue, pointerReflectValue); ok { + return err + } + + // Normal unmarshalling interfaces checks. + if ok, err = bindVarToReflectValueWithInterfaceCheck(pointerReflectValue, paramsInterface); ok { + return err + } + + // It automatically creates struct object if necessary. + // 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()) + 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().(localinterface.IUnmarshalValue); ok { + // return v.UnmarshalValue(params) + // } + // Note that it's `pointerElemReflectValue` here not `pointerReflectValue`. + if ok, err = bindVarToReflectValueWithInterfaceCheck(pointerElemReflectValue, paramsInterface); ok { + return err + } + // Retrieve its element, may be struct at last. + pointerElemReflectValue = pointerElemReflectValue.Elem() + } + paramsMap, ok := paramsInterface.(map[string]any) + if !ok { + // paramsMap is the map[string]any type variable for params. + // DO NOT use MapDeep here. + paramsMap = c.doMapConvert(paramsInterface, recursiveTypeAuto, true) + if paramsMap == nil { + return gerror.NewCodef( + gcode.CodeInvalidParameter, + `convert params from "%#v" to "map[string]any" failed`, + params, + ) + } + } + // Nothing to be done as the parameters are empty. + if len(paramsMap) == 0 { + return nil + } + // Get struct info from cache or parse struct and cache the struct info. + cachedStructInfo := c.internalConvertConfig.GetCachedStructInfo( + pointerElemReflectValue.Type(), priorityTag, + ) + // Nothing to be converted. + if cachedStructInfo == nil { + return nil + } + // For the structure types of 0 tagOrFiledNameToFieldInfoMap, + // they also need to be cached to prevent invalid logic + if cachedStructInfo.HasNoFields() { + return nil + } + var ( + // Indicates that those values have been used and cannot be reused. + usedParamsKeyOrTagNameMap = structcache.GetUsedParamsKeyOrTagNameMapFromPool() + cachedFieldInfo *structcache.CachedFieldInfo + paramsValue any + ) + defer structcache.PutUsedParamsKeyOrTagNameMapToPool(usedParamsKeyOrTagNameMap) + + // Firstly, search according to custom mapping rules. + // If a possible direct assignment is found, reduce the number of subsequent map searches. + for paramKey, fieldName := range paramKeyToAttrMap { + paramsValue, ok = paramsMap[paramKey] + if !ok { + continue + } + cachedFieldInfo = cachedStructInfo.GetFieldInfo(fieldName) + if cachedFieldInfo != nil { + fieldValue := cachedFieldInfo.GetFieldReflectValueFrom(pointerElemReflectValue) + if err = c.bindVarToStructField( + cachedFieldInfo, + fieldValue, + paramsValue, + paramKeyToAttrMap, + ); err != nil { + return err + } + if len(cachedFieldInfo.OtherSameNameField) > 0 { + if err = c.setOtherSameNameField( + cachedFieldInfo, paramsValue, pointerReflectValue, paramKeyToAttrMap, + ); err != nil { + return err + } + } + usedParamsKeyOrTagNameMap[paramKey] = struct{}{} + } + } + // Already done converting for given `paramsMap`. + if len(usedParamsKeyOrTagNameMap) == len(paramsMap) { + return nil + } + return c.bindStructWithLoopFieldInfos( + paramsMap, pointerElemReflectValue, paramKeyToAttrMap, usedParamsKeyOrTagNameMap, cachedStructInfo, + ) +} + +func (c *Converter) setOtherSameNameField( + cachedFieldInfo *structcache.CachedFieldInfo, + srcValue any, + structValue reflect.Value, + paramKeyToAttrMap map[string]string, +) (err error) { + // loop the same field name of all sub attributes. + for _, otherFieldInfo := range cachedFieldInfo.OtherSameNameField { + fieldValue := cachedFieldInfo.GetOtherFieldReflectValueFrom(structValue, otherFieldInfo.FieldIndexes) + if err = c.bindVarToStructField(otherFieldInfo, fieldValue, srcValue, paramKeyToAttrMap); err != nil { + return err + } + } + return nil +} + +func (c *Converter) bindStructWithLoopFieldInfos( + paramsMap map[string]any, + structValue reflect.Value, + paramKeyToAttrMap map[string]string, + usedParamsKeyOrTagNameMap map[string]struct{}, + cachedStructInfo *structcache.CachedStructInfo, +) (err error) { + var ( + cachedFieldInfo *structcache.CachedFieldInfo + fuzzLastKey string + fieldValue reflect.Value + paramKey string + paramValue any + matched bool + ok bool + ) + for _, cachedFieldInfo = range cachedStructInfo.GetFieldConvertInfos() { + for _, fieldTag := range cachedFieldInfo.PriorityTagAndFieldName { + if paramValue, ok = paramsMap[fieldTag]; !ok { + continue + } + fieldValue = cachedFieldInfo.GetFieldReflectValueFrom(structValue) + if err = c.bindVarToStructField( + cachedFieldInfo, fieldValue, paramValue, paramKeyToAttrMap, + ); err != nil { + return err + } + // handle same field name in nested struct. + if len(cachedFieldInfo.OtherSameNameField) > 0 { + if err = c.setOtherSameNameField( + cachedFieldInfo, paramValue, structValue, paramKeyToAttrMap, + ); err != nil { + return err + } + } + usedParamsKeyOrTagNameMap[fieldTag] = struct{}{} + matched = true + break + } + if matched { + matched = false + continue + } + + fuzzLastKey = cachedFieldInfo.LastFuzzyKey.Load().(string) + if paramValue, ok = paramsMap[fuzzLastKey]; !ok { + paramKey, paramValue = fuzzyMatchingFieldName( + cachedFieldInfo.RemoveSymbolsFieldName, paramsMap, usedParamsKeyOrTagNameMap, + ) + ok = paramKey != "" + cachedFieldInfo.LastFuzzyKey.Store(paramKey) + } + if ok { + fieldValue = cachedFieldInfo.GetFieldReflectValueFrom(structValue) + if paramValue != nil { + if err = c.bindVarToStructField( + cachedFieldInfo, fieldValue, paramValue, paramKeyToAttrMap, + ); err != nil { + return err + } + // handle same field name in nested struct. + if len(cachedFieldInfo.OtherSameNameField) > 0 { + if err = c.setOtherSameNameField( + cachedFieldInfo, paramValue, structValue, paramKeyToAttrMap, + ); err != nil { + return err + } + } + } + usedParamsKeyOrTagNameMap[paramKey] = struct{}{} + } + } + return nil +} + +// fuzzy matching rule: +// to match field name and param key in case-insensitive and without symbols. +func fuzzyMatchingFieldName( + fieldName string, + paramsMap map[string]any, + usedParamsKeyMap map[string]struct{}, +) (string, any) { + for paramKey, paramValue := range paramsMap { + if _, ok := usedParamsKeyMap[paramKey]; ok { + continue + } + removeParamKeyUnderline := utils.RemoveSymbols(paramKey) + if strings.EqualFold(fieldName, removeParamKeyUnderline) { + return paramKey, paramValue + } + } + return "", nil +} + +// bindVarToStructField sets value to struct object attribute by name. +// each value to attribute converting comes into in this function. +func (c *Converter) bindVarToStructField( + cachedFieldInfo *structcache.CachedFieldInfo, + fieldValue reflect.Value, + srcValue any, + paramKeyToAttrMap map[string]string, +) (err error) { + if !fieldValue.IsValid() { + return nil + } + // CanSet checks whether attribute is public accessible. + if !fieldValue.CanSet() { + return nil + } + defer func() { + if exception := recover(); exception != nil { + if err = c.bindVarToReflectValue(fieldValue, srcValue, paramKeyToAttrMap); err != nil { + err = gerror.Wrapf(err, `error binding srcValue to attribute "%s"`, cachedFieldInfo.FieldName()) + } + } + }() + // Directly converting. + if empty.IsNil(srcValue) { + fieldValue.Set(reflect.Zero(fieldValue.Type())) + return nil + } + // Try to call custom converter. + // Issue: https://github.com/gogf/gf/issues/3099 + var ( + customConverterInput reflect.Value + ok bool + ) + if cachedFieldInfo.IsCustomConvert { + if customConverterInput, ok = srcValue.(reflect.Value); !ok { + customConverterInput = reflect.ValueOf(srcValue) + } + if ok, err = c.callCustomConverter(customConverterInput, fieldValue); ok || err != nil { + return + } + } + if cachedFieldInfo.IsCommonInterface { + if ok, err = bindVarToReflectValueWithInterfaceCheck(fieldValue, srcValue); ok || err != nil { + return + } + } + // Common types use fast assignment logic + if cachedFieldInfo.ConvertFunc != nil { + return cachedFieldInfo.ConvertFunc(srcValue, fieldValue) + } + c.doConvertWithReflectValueSet( + 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 any) (bool, error) { + var pointer any + if reflectValue.Kind() != reflect.Ptr && reflectValue.CanAddr() { + reflectValueAddr := reflectValue.Addr() + if reflectValueAddr.IsNil() || !reflectValueAddr.IsValid() { + return false, nil + } + // Not a pointer, but can token address, that makes it can be unmarshalled. + pointer = reflectValue.Addr().Interface() + } else { + if reflectValue.IsNil() || !reflectValue.IsValid() { + return false, nil + } + pointer = reflectValue.Interface() + } + // UnmarshalValue. + if v, ok := pointer.(localinterface.IUnmarshalValue); ok { + return ok, v.UnmarshalValue(value) + } + // UnmarshalText. + if v, ok := pointer.(localinterface.IUnmarshalText); ok { + var valueBytes []byte + if b, ok := value.([]byte); ok { + valueBytes = b + } else if s, ok := value.(string); ok { + valueBytes = []byte(s) + } else if f, ok := value.(localinterface.IString); ok { + valueBytes = []byte(f.String()) + } + if len(valueBytes) > 0 { + return ok, v.UnmarshalText(valueBytes) + } + } + // UnmarshalJSON. + if v, ok := pointer.(localinterface.IUnmarshalJSON); ok { + var valueBytes []byte + if b, ok := value.([]byte); ok { + valueBytes = b + } else if s, ok := value.(string); ok { + valueBytes = []byte(s) + } else if f, ok := value.(localinterface.IString); ok { + valueBytes = []byte(f.String()) + } + + if len(valueBytes) > 0 { + // If it is not a valid JSON string, it then adds char `"` on its both sides to make it is. + if !json.Valid(valueBytes) { + newValueBytes := make([]byte, len(valueBytes)+2) + newValueBytes[0] = '"' + newValueBytes[len(newValueBytes)-1] = '"' + copy(newValueBytes[1:], valueBytes) + valueBytes = newValueBytes + } + return ok, v.UnmarshalJSON(valueBytes) + } + } + if v, ok := pointer.(localinterface.ISet); ok { + v.Set(value) + return ok, nil + } + return false, nil +} + +// bindVarToReflectValue sets `value` to reflect value object `structFieldValue`. +func (c *Converter) bindVarToReflectValue( + structFieldValue reflect.Value, value any, paramKeyToAttrMap map[string]string, +) (err error) { + // JSON content converting. + ok, err := doConvertWithJsonCheck(value, structFieldValue) + if err != nil { + return err + } + if ok { + return nil + } + + kind := structFieldValue.Kind() + // Converting using `Set` interface implements, for some types. + switch kind { + case reflect.Slice, reflect.Array, reflect.Ptr, reflect.Interface: + if !structFieldValue.IsNil() { + if v, ok := structFieldValue.Interface().(localinterface.ISet); ok { + v.Set(value) + return nil + } + } + default: + } + + // Converting using reflection by kind. + switch kind { + case reflect.Map: + return c.MapToMap(value, structFieldValue, paramKeyToAttrMap) + + case reflect.Struct: + // Recursively converting for struct attribute. + if err = c.Struct(value, structFieldValue, nil, ""); err != nil { + // Note there's reflect conversion mechanism here. + structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type())) + } + + // Note that the slice element might be type of struct, + // so it uses Struct function doing the converting internally. + case reflect.Slice, reflect.Array: + var ( + reflectArray reflect.Value + reflectValue = reflect.ValueOf(value) + ) + if reflectValue.Kind() == reflect.Slice || reflectValue.Kind() == reflect.Array { + reflectArray = reflect.MakeSlice(structFieldValue.Type(), reflectValue.Len(), reflectValue.Len()) + if reflectValue.Len() > 0 { + var ( + elemType = reflectArray.Index(0).Type() + elemTypeName string + converted bool + ) + for i := 0; i < reflectValue.Len(); i++ { + converted = false + elemTypeName = elemType.Name() + if elemTypeName == "" { + elemTypeName = elemType.String() + } + var elem reflect.Value + if elemType.Kind() == reflect.Ptr { + elem = reflect.New(elemType.Elem()).Elem() + } else { + elem = reflect.New(elemType).Elem() + } + if elem.Kind() == reflect.Struct { + if err = c.Struct(reflectValue.Index(i).Interface(), elem, nil, ""); err == nil { + converted = true + } + } + if !converted { + c.doConvertWithReflectValueSet( + 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. + elem = elem.Addr() + } + reflectArray.Index(i).Set(elem) + } + } + } else { + var ( + elem reflect.Value + elemType = structFieldValue.Type().Elem() + elemTypeName = elemType.Name() + converted bool + ) + switch reflectValue.Kind() { + case reflect.String: + // Value is empty string. + if reflectValue.IsZero() { + var elemKind = elemType.Kind() + // Try to find the original type kind of the slice element. + if elemKind == reflect.Ptr { + elemKind = elemType.Elem().Kind() + } + switch elemKind { + case reflect.String: + // Empty string cannot be assigned to string slice. + return nil + default: + } + } + default: + } + if elemTypeName == "" { + elemTypeName = elemType.String() + } + if elemType.Kind() == reflect.Ptr { + elem = reflect.New(elemType.Elem()).Elem() + } else { + elem = reflect.New(elemType).Elem() + } + if elem.Kind() == reflect.Struct { + if err = c.Struct(value, elem, nil, ""); err == nil { + converted = true + } + } + if !converted { + c.doConvertWithReflectValueSet( + 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. + elem = elem.Addr() + } + reflectArray = reflect.MakeSlice(structFieldValue.Type(), 1, 1) + reflectArray.Index(0).Set(elem) + } + structFieldValue.Set(reflectArray) + + case reflect.Ptr: + if structFieldValue.IsNil() || structFieldValue.IsZero() { + // Nil or empty pointer, it creates a new one. + item := reflect.New(structFieldValue.Type().Elem()) + if ok, err = bindVarToReflectValueWithInterfaceCheck(item, value); ok { + structFieldValue.Set(item) + return err + } + elem := item.Elem() + if err = c.bindVarToReflectValue(elem, value, paramKeyToAttrMap); err == nil { + structFieldValue.Set(elem.Addr()) + } + } else { + // Not empty pointer, it assigns values to it. + return c.bindVarToReflectValue(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((*any)(nil))) + } else { + // Note there's reflect conversion mechanism here. + structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type())) + } + + default: + defer func() { + if exception := recover(); exception != nil { + err = gerror.NewCodef( + gcode.CodeInternalPanic, + `cannot convert value "%+v" to type "%s":%+v`, + value, + structFieldValue.Type().String(), + exception, + ) + } + }() + // It here uses reflect converting `value` to type of the attribute and assigns + // the result value to the attribute. It might fail and panic if the usual Go + // conversion rules do not allow conversion. + structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type())) + } + return nil +} diff --git a/util/gconv/gconv_converter_structs.go b/util/gconv/gconv_converter_structs.go new file mode 100644 index 000000000..347cd5cbd --- /dev/null +++ b/util/gconv/gconv_converter_structs.go @@ -0,0 +1,115 @@ +// 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" +) + +// Structs converts any slice to given struct slice. +// +// It automatically checks and converts json string to []map if `params` is string/[]byte. +// +// The parameter `pointer` should be type of pointer to slice of struct. +// Note that if `pointer` is a pointer to another pointer of type of slice of struct, +// it will create the struct/pointer internally. +func (c *Converter) Structs( + params any, pointer any, paramKeyToAttrMap map[string]string, priorityTag string, +) (err error) { + defer func() { + // Catch the panic, especially the reflection operation panics. + if exception := recover(); exception != nil { + if v, ok := exception.(error); ok && gerror.HasStack(v) { + err = v + } else { + err = gerror.NewCodeSkipf(gcode.CodeInternalPanic, 1, "%+v", exception) + } + } + }() + + // Pointer type check. + pointerRv, ok := pointer.(reflect.Value) + if !ok { + pointerRv = reflect.ValueOf(pointer) + if kind := pointerRv.Kind(); kind != reflect.Ptr { + return gerror.NewCodef( + gcode.CodeInvalidParameter, + "pointer should be type of pointer, but got: %v", kind, + ) + } + } + // Converting `params` to map slice. + var ( + paramsList []any + paramsRv = reflect.ValueOf(params) + paramsKind = paramsRv.Kind() + ) + for paramsKind == reflect.Ptr { + paramsRv = paramsRv.Elem() + paramsKind = paramsRv.Kind() + } + switch paramsKind { + case reflect.Slice, reflect.Array: + paramsList = make([]any, paramsRv.Len()) + for i := 0; i < paramsRv.Len(); i++ { + paramsList[i] = paramsRv.Index(i).Interface() + } + default: + var paramsMaps = Maps(params) + paramsList = make([]any, len(paramsMaps)) + for i := 0; i < len(paramsMaps); i++ { + paramsList[i] = paramsMaps[i] + } + } + // If `params` is an empty slice, no conversion. + if len(paramsList) == 0 { + return nil + } + var ( + reflectElemArray = reflect.MakeSlice(pointerRv.Type().Elem(), len(paramsList), len(paramsList)) + itemType = reflectElemArray.Index(0).Type() + itemTypeKind = itemType.Kind() + pointerRvElem = pointerRv.Elem() + pointerRvLength = pointerRvElem.Len() + ) + if itemTypeKind == reflect.Ptr { + // Pointer element. + for i := 0; i < len(paramsList); i++ { + var tempReflectValue reflect.Value + if i < pointerRvLength { + // Might be nil. + tempReflectValue = pointerRvElem.Index(i).Elem() + } + if !tempReflectValue.IsValid() { + tempReflectValue = reflect.New(itemType.Elem()).Elem() + } + if err = c.Struct(paramsList[i], tempReflectValue, paramKeyToAttrMap, priorityTag); err != nil { + return err + } + reflectElemArray.Index(i).Set(tempReflectValue.Addr()) + } + } else { + // Struct element. + for i := 0; i < len(paramsList); i++ { + var tempReflectValue reflect.Value + if i < pointerRvLength { + tempReflectValue = pointerRvElem.Index(i) + } else { + tempReflectValue = reflect.New(itemType).Elem() + } + if err = c.Struct(paramsList[i], tempReflectValue, paramKeyToAttrMap, priorityTag); err != nil { + return err + } + reflectElemArray.Index(i).Set(tempReflectValue) + } + } + pointerRv.Elem().Set(reflectElemArray) + return nil +} diff --git a/util/gconv/gconv_converter_time.go b/util/gconv/gconv_converter_time.go new file mode 100644 index 000000000..f1b185758 --- /dev/null +++ b/util/gconv/gconv_converter_time.go @@ -0,0 +1,111 @@ +// 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 ( + "time" + + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/internal/utils" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +// Time converts `any` to time.Time. +func (c *Converter) Time(any interface{}, format ...string) (time.Time, error) { + // It's already this type. + if len(format) == 0 { + if v, ok := any.(time.Time); ok { + return v, nil + } + } + t, err := c.GTime(any, format...) + if err != nil { + return time.Time{}, err + } + if t != nil { + return t.Time, nil + } + return time.Time{}, nil +} + +// Duration converts `any` to time.Duration. +// If `any` is string, then it uses time.ParseDuration to convert it. +// If `any` is numeric, then it converts `any` as nanoseconds. +func (c *Converter) Duration(any interface{}) (time.Duration, error) { + // It's already this type. + if v, ok := any.(time.Duration); ok { + return v, nil + } + s, err := c.String(any) + if err != nil { + return 0, err + } + if !utils.IsNumeric(s) { + return gtime.ParseDuration(s) + } + i, err := c.Int64(any) + if err != nil { + return 0, err + } + return time.Duration(i), nil +} + +// GTime converts `any` to *gtime.Time. +// The parameter `format` can be used to specify the format of `any`. +// It returns the converted value that matched the first format of the formats slice. +// If no `format` given, it converts `any` using gtime.NewFromTimeStamp if `any` is numeric, +// or using gtime.StrToTime if `any` is string. +func (c *Converter) GTime(any interface{}, format ...string) (*gtime.Time, error) { + if empty.IsNil(any) { + return nil, nil + } + if v, ok := any.(localinterface.IGTime); ok { + return v.GTime(format...), nil + } + // It's already this type. + if len(format) == 0 { + if v, ok := any.(*gtime.Time); ok { + return v, nil + } + if t, ok := any.(time.Time); ok { + return gtime.New(t), nil + } + if t, ok := any.(*time.Time); ok { + return gtime.New(t), nil + } + } + s, err := c.String(any) + if err != nil { + return nil, err + } + if len(s) == 0 { + return gtime.New(), nil + } + // Priority conversion using given format. + if len(format) > 0 { + for _, item := range format { + t, err := gtime.StrToTimeFormat(s, item) + if err != nil { + return nil, err + } + if t != nil { + return t, nil + } + } + return nil, nil + } + if utils.IsNumeric(s) { + i, err := c.Int64(s) + if err != nil { + return nil, err + } + return gtime.NewFromTimeStamp(i), nil + } else { + return gtime.StrToTime(s) + } +} diff --git a/util/gconv/gconv_converter_uint.go b/util/gconv/gconv_converter_uint.go new file mode 100644 index 000000000..a17ef2a7a --- /dev/null +++ b/util/gconv/gconv_converter_uint.go @@ -0,0 +1,156 @@ +// 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 ( + "math" + "reflect" + "strconv" + + "github.com/gogf/gf/v2/encoding/gbinary" + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +func (c *Converter) Uint(any any) (uint, error) { + if empty.IsNil(any) { + return 0, nil + } + if v, ok := any.(uint); ok { + return v, nil + } + v, err := c.Uint64(any) + return uint(v), err +} + +func (c *Converter) Uint8(any any) (uint8, error) { + if empty.IsNil(any) { + return 0, nil + } + if v, ok := any.(uint8); ok { + return v, nil + } + v, err := c.Uint64(any) + return uint8(v), err +} + +func (c *Converter) Uint16(any any) (uint16, error) { + if empty.IsNil(any) { + return 0, nil + } + if v, ok := any.(uint16); ok { + return v, nil + } + v, err := c.Uint64(any) + return uint16(v), err +} + +func (c *Converter) Uint32(any any) (uint32, error) { + if empty.IsNil(any) { + return 0, nil + } + if v, ok := any.(uint32); ok { + return v, nil + } + v, err := c.Uint64(any) + return uint32(v), err +} + +func (c *Converter) Uint64(any any) (uint64, error) { + if empty.IsNil(any) { + return 0, nil + } + if v, ok := any.(uint64); ok { + return v, nil + } + rv := reflect.ValueOf(any) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + val := rv.Int() + if val < 0 { + return uint64(val), gerror.NewCodef( + gcode.CodeInvalidParameter, + `cannot convert negative value "%d" to uint64`, + val, + ) + } + return uint64(val), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return rv.Uint(), nil + case reflect.Uintptr: + return rv.Uint(), nil + case reflect.Float32, reflect.Float64: + val := rv.Float() + if val < 0 { + return uint64(val), gerror.NewCodef( + gcode.CodeInvalidParameter, + `cannot convert negative value "%f" to uint64`, + val, + ) + } + return uint64(val), nil + case reflect.Bool: + if rv.Bool() { + return 1, nil + } + return 0, nil + case reflect.Ptr: + if rv.IsNil() { + return 0, nil + } + if f, ok := any.(localinterface.IUint64); ok { + return f.Uint64(), nil + } + return c.Uint64(rv.Elem().Interface()) + case reflect.Slice: + if rv.Type().Elem().Kind() == reflect.Uint8 { + return gbinary.DecodeToUint64(rv.Bytes()), nil + } + return 0, gerror.NewCodef( + gcode.CodeInvalidParameter, + `unsupport slice type "%s" for converting to uint64`, + rv.Type().String(), + ) + case reflect.String: + var s = rv.String() + // Hexadecimal + if len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') { + v, err := strconv.ParseUint(s[2:], 16, 64) + if err == nil { + return v, nil + } + return 0, gerror.WrapCodef( + gcode.CodeInvalidParameter, + err, + `cannot convert hexadecimal string "%s" to uint64`, + s, + ) + } + // Decimal + if v, err := strconv.ParseUint(s, 10, 64); err == nil { + return v, nil + } + // Float64 + if v, err := c.Float64(any); err == nil { + if math.IsNaN(v) { + return 0, nil + } + return uint64(v), nil + } + default: + if f, ok := any.(localinterface.IUint64); ok { + return f.Uint64(), nil + } + } + return 0, gerror.NewCodef( + gcode.CodeInvalidParameter, + `unsupport value type "%s" for converting to uint64`, + reflect.TypeOf(any).String(), + ) +} diff --git a/util/gconv/gconv_float.go b/util/gconv/gconv_float.go index fac2d1c98..62d6d4861 100644 --- a/util/gconv/gconv_float.go +++ b/util/gconv/gconv_float.go @@ -6,144 +6,14 @@ package gconv -import ( - "reflect" - "strconv" - - "github.com/gogf/gf/v2/encoding/gbinary" - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/internal/empty" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" -) - // Float32 converts `any` to float32. func Float32(any any) float32 { - v, _ := doFloat32(any) + v, _ := defaultConverter.Float32(any) return v } -func doFloat32(any any) (float32, error) { - if empty.IsNil(any) { - return 0, nil - } - switch value := any.(type) { - case float32: - return value, nil - case float64: - return float32(value), nil - case []byte: - // TODO: It might panic here for these types. - return gbinary.DecodeToFloat32(value), nil - default: - rv := reflect.ValueOf(any) - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return float32(rv.Int()), nil - case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return float32(rv.Uint()), nil - case reflect.Float32, reflect.Float64: - return float32(rv.Float()), nil - case reflect.Bool: - if rv.Bool() { - return 1, nil - } - return 0, nil - case reflect.String: - f, err := strconv.ParseFloat(rv.String(), 32) - if err != nil { - return 0, gerror.WrapCodef( - gcode.CodeInvalidParameter, err, "converting string to float32 failed for: %v", any, - ) - } - return float32(f), nil - case reflect.Ptr: - if rv.IsNil() { - return 0, nil - } - if f, ok := value.(localinterface.IFloat32); ok { - return f.Float32(), nil - } - return doFloat32(rv.Elem().Interface()) - default: - if f, ok := value.(localinterface.IFloat32); ok { - return f.Float32(), nil - } - v, err := strconv.ParseFloat(String(any), 32) - if err != nil { - return 0, gerror.WrapCodef( - gcode.CodeInvalidParameter, err, "converting string to float32 failed for: %v", any, - ) - } - return float32(v), nil - } - } -} - // Float64 converts `any` to float64. func Float64(any any) float64 { - v, _ := doFloat64(any) + v, _ := defaultConverter.Float64(any) return v } - -func doFloat64(any any) (float64, error) { - if empty.IsNil(any) { - return 0, nil - } - switch value := any.(type) { - case float32: - return float64(value), nil - case float64: - return value, nil - case []byte: - // TODO: It might panic here for these types. - return gbinary.DecodeToFloat64(value), nil - default: - rv := reflect.ValueOf(any) - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return float64(rv.Int()), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return float64(rv.Uint()), nil - case reflect.Uintptr: - return float64(rv.Uint()), nil - case reflect.Float32, reflect.Float64: - // Please Note: - // When the type is float32 or a custom type defined based on float32, - // switching to float64 may result in a few extra decimal places. - return rv.Float(), nil - case reflect.Bool: - if rv.Bool() { - return 1, nil - } - return 0, nil - case reflect.String: - f, err := strconv.ParseFloat(rv.String(), 64) - if err != nil { - return 0, gerror.WrapCodef( - gcode.CodeInvalidParameter, err, "converting string to float64 failed for: %v", any, - ) - } - return f, nil - case reflect.Ptr: - if rv.IsNil() { - return 0, nil - } - if f, ok := value.(localinterface.IFloat64); ok { - return f.Float64(), nil - } - return doFloat64(rv.Elem().Interface()) - default: - if f, ok := value.(localinterface.IFloat64); ok { - return f.Float64(), nil - } - v, err := strconv.ParseFloat(String(any), 64) - if err != nil { - return 0, gerror.WrapCodef( - gcode.CodeInvalidParameter, err, "converting string to float64 failed for: %v", any, - ) - } - return v, nil - } - } -} diff --git a/util/gconv/gconv_int.go b/util/gconv/gconv_int.go index a3f833ae6..78c534d6f 100644 --- a/util/gconv/gconv_int.go +++ b/util/gconv/gconv_int.go @@ -6,177 +6,32 @@ package gconv -import ( - "math" - "reflect" - "strconv" - - "github.com/gogf/gf/v2/encoding/gbinary" - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/internal/empty" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" -) - // Int converts `any` to int. func Int(any any) int { - v, _ := doInt(any) + v, _ := defaultConverter.Int(any) return v } -func doInt(any any) (int, error) { - if v, ok := any.(int); ok { - return v, nil - } - v, err := doInt64(any) - if err != nil { - return 0, err - } - return int(v), nil -} - // Int8 converts `any` to int8. func Int8(any any) int8 { - v, _ := doInt8(any) + v, _ := defaultConverter.Int8(any) return v } -func doInt8(any any) (int8, error) { - if v, ok := any.(int8); ok { - return v, nil - } - v, err := doInt64(any) - if err != nil { - return 0, err - } - return int8(v), nil -} - // Int16 converts `any` to int16. func Int16(any any) int16 { - v, _ := doInt16(any) + v, _ := defaultConverter.Int16(any) return v } -func doInt16(any any) (int16, error) { - if v, ok := any.(int16); ok { - return v, nil - } - v, err := doInt64(any) - if err != nil { - return 0, err - } - return int16(v), nil -} - // Int32 converts `any` to int32. func Int32(any any) int32 { - v, _ := doInt32(any) + v, _ := defaultConverter.Int32(any) return v } -func doInt32(any any) (int32, error) { - if v, ok := any.(int32); ok { - return v, nil - } - v, err := doInt64(any) - if err != nil { - return 0, err - } - return int32(v), nil -} - // Int64 converts `any` to int64. func Int64(any any) int64 { - v, _ := doInt64(any) + v, _ := defaultConverter.Int64(any) return v } - -func doInt64(any any) (int64, error) { - if empty.IsNil(any) { - return 0, nil - } - if v, ok := any.(int64); ok { - return v, nil - } - rv := reflect.ValueOf(any) - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return rv.Int(), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return int64(rv.Uint()), nil - case reflect.Uintptr: - return int64(rv.Uint()), nil - case reflect.Float32, reflect.Float64: - return int64(rv.Float()), nil - case reflect.Bool: - if rv.Bool() { - return 1, nil - } - return 0, nil - case reflect.Ptr: - if rv.IsNil() { - return 0, nil - } - if f, ok := any.(localinterface.IInt64); ok { - return f.Int64(), nil - } - return doInt64(rv.Elem().Interface()) - case reflect.Slice: - // TODO: It might panic here for these types. - if rv.Type().Elem().Kind() == reflect.Uint8 { - return gbinary.DecodeToInt64(rv.Bytes()), nil - } - case reflect.String: - var ( - s = rv.String() - isMinus = false - ) - if len(s) > 0 { - if s[0] == '-' { - isMinus = true - s = s[1:] - } else if s[0] == '+' { - s = s[1:] - } - } - // Hexadecimal. - if len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') { - if v, e := strconv.ParseInt(s[2:], 16, 64); e == nil { - if isMinus { - return -v, nil - } - return v, nil - } - } - // Decimal. - if v, e := strconv.ParseInt(s, 10, 64); e == nil { - if isMinus { - return -v, nil - } - return v, nil - } - // Float64. - valueInt64, err := doFloat64(s) - if err != nil { - return 0, err - } - if math.IsNaN(valueInt64) { - return 0, nil - } else { - if isMinus { - return -int64(valueInt64), nil - } - return int64(valueInt64), nil - } - default: - if f, ok := any.(localinterface.IInt64); ok { - return f.Int64(), nil - } - } - return 0, gerror.NewCodef( - gcode.CodeInvalidParameter, - `unsupport value type for converting to int64: %v`, - reflect.TypeOf(any), - ) -} diff --git a/util/gconv/gconv_map.go b/util/gconv/gconv_map.go index 46809da6f..8523c3a42 100644 --- a/util/gconv/gconv_map.go +++ b/util/gconv/gconv_map.go @@ -6,17 +6,6 @@ package gconv -import ( - "reflect" - "strings" - - "github.com/gogf/gf/v2/internal/empty" - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/internal/utils" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" - "github.com/gogf/gf/v2/util/gtag" -) - type recursiveType string const ( @@ -28,7 +17,7 @@ const ( type MapOption struct { // Deep marks doing Map function recursively, which means if the attribute of given converting value // is also a struct/*struct, it automatically calls Map function on this attribute converting it to - // a map[string]interface{} type variable. + // a map[string]any type variable. Deep bool // OmitEmpty ignores the attributes that has json `omitempty` tag. @@ -38,516 +27,30 @@ type MapOption struct { Tags []string } -// Map converts any variable `value` to map[string]interface{}. If the parameter `value` is not a +// Map converts any variable `value` to map[string]any. If the parameter `value` is not a // map/struct/*struct type, then the conversion will fail and returns nil. // // If `value` is a struct/*struct object, the second parameter `priorityTagAndFieldName` specifies the most priority // priorityTagAndFieldName that will be detected, otherwise it detects the priorityTagAndFieldName in order of: // gconv, json, field name. -func Map(value interface{}, option ...MapOption) map[string]interface{} { - return doMapConvert(value, recursiveTypeAuto, false, option...) +func Map(value any, option ...MapOption) map[string]any { + return defaultConverter.doMapConvert(value, recursiveTypeAuto, false, option...) } // MapDeep does Map function recursively, which means if the attribute of `value` // is also a struct/*struct, calls Map function on this attribute converting it to -// a map[string]interface{} type variable. +// a map[string]any type variable. // Deprecated: used Map instead. -func MapDeep(value interface{}, tags ...string) map[string]interface{} { - return doMapConvert(value, recursiveTypeTrue, false, MapOption{ +func MapDeep(value any, tags ...string) map[string]any { + return defaultConverter.doMapConvert(value, recursiveTypeTrue, false, MapOption{ Deep: true, Tags: tags, }) } -// doMapConvert implements the map converting. -// It automatically checks and converts json string to map if `value` is string/[]byte. -// -// TODO completely implement the recursive converting for all types, especially the map. -func doMapConvert(value interface{}, recursive recursiveType, mustMapReturn bool, option ...MapOption) map[string]interface{} { - if value == nil { - return nil - } - // It redirects to its underlying value if it has implemented interface iVal. - if v, ok := value.(localinterface.IVal); ok { - value = v.Val() - } - var ( - usedOption = getUsedMapOption(option...) - newTags = gtag.StructTagPriority - ) - if usedOption.Deep { - recursive = recursiveTypeTrue - } - switch len(usedOption.Tags) { - case 0: - // No need handling. - case 1: - newTags = append(strings.Split(usedOption.Tags[0], ","), gtag.StructTagPriority...) - default: - newTags = append(usedOption.Tags, gtag.StructTagPriority...) - } - // Assert the common combination of types, and finally it uses reflection. - dataMap := make(map[string]interface{}) - switch r := value.(type) { - case string: - // If it is a JSON string, automatically unmarshal it! - if len(r) > 0 && r[0] == '{' && r[len(r)-1] == '}' { - if err := json.UnmarshalUseNumber([]byte(r), &dataMap); err != nil { - return nil - } - } else { - return nil - } - case []byte: - // If it is a JSON string, automatically unmarshal it! - if len(r) > 0 && r[0] == '{' && r[len(r)-1] == '}' { - if err := json.UnmarshalUseNumber(r, &dataMap); err != nil { - return nil - } - } else { - return nil - } - case map[interface{}]interface{}: - recursiveOption := usedOption - recursiveOption.Tags = newTags - for k, v := range r { - dataMap[String(k)] = doMapConvertForMapOrStructValue( - doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: v, - RecursiveType: recursive, - RecursiveOption: recursive == recursiveTypeTrue, - Option: recursiveOption, - }, - ) - } - case map[interface{}]string: - for k, v := range r { - dataMap[String(k)] = v - } - case map[interface{}]int: - for k, v := range r { - dataMap[String(k)] = v - } - case map[interface{}]uint: - for k, v := range r { - dataMap[String(k)] = v - } - case map[interface{}]float32: - for k, v := range r { - dataMap[String(k)] = v - } - case map[interface{}]float64: - for k, v := range r { - dataMap[String(k)] = v - } - case map[string]bool: - for k, v := range r { - dataMap[k] = v - } - case map[string]int: - for k, v := range r { - dataMap[k] = v - } - case map[string]uint: - for k, v := range r { - dataMap[k] = v - } - case map[string]float32: - for k, v := range r { - dataMap[k] = v - } - case map[string]float64: - for k, v := range r { - dataMap[k] = v - } - case map[string]string: - for k, v := range r { - dataMap[k] = v - } - case map[string]interface{}: - if recursive == recursiveTypeTrue { - recursiveOption := usedOption - recursiveOption.Tags = newTags - // A copy of current map. - for k, v := range r { - dataMap[k] = doMapConvertForMapOrStructValue( - doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: v, - RecursiveType: recursive, - RecursiveOption: recursive == recursiveTypeTrue, - Option: recursiveOption, - }, - ) - } - } else { - // It returns the map directly without any changing. - return r - } - case map[int]interface{}: - recursiveOption := usedOption - recursiveOption.Tags = newTags - for k, v := range r { - dataMap[String(k)] = doMapConvertForMapOrStructValue( - doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: v, - RecursiveType: recursive, - RecursiveOption: recursive == recursiveTypeTrue, - Option: recursiveOption, - }, - ) - } - case map[int]string: - for k, v := range r { - dataMap[String(k)] = v - } - case map[uint]string: - for k, v := range r { - dataMap[String(k)] = v - } - - default: - // Not a common type, it then uses reflection for conversion. - var reflectValue reflect.Value - if v, ok := value.(reflect.Value); ok { - reflectValue = v - } else { - reflectValue = reflect.ValueOf(value) - } - reflectKind := reflectValue.Kind() - // If it is a pointer, we should find its real data type. - for reflectKind == reflect.Ptr { - reflectValue = reflectValue.Elem() - reflectKind = reflectValue.Kind() - } - switch reflectKind { - // If `value` is type of array, it converts the value of even number index as its key and - // the value of odd number index as its corresponding value, for example: - // []string{"k1","v1","k2","v2"} => map[string]interface{}{"k1":"v1", "k2":"v2"} - // []string{"k1","v1","k2"} => map[string]interface{}{"k1":"v1", "k2":nil} - case reflect.Slice, reflect.Array: - length := reflectValue.Len() - for i := 0; i < length; i += 2 { - if i+1 < length { - dataMap[String(reflectValue.Index(i).Interface())] = reflectValue.Index(i + 1).Interface() - } else { - dataMap[String(reflectValue.Index(i).Interface())] = nil - } - } - case reflect.Map, reflect.Struct, reflect.Interface: - recursiveOption := usedOption - recursiveOption.Tags = newTags - convertedValue := doMapConvertForMapOrStructValue( - doMapConvertForMapOrStructValueInput{ - IsRoot: true, - Value: value, - RecursiveType: recursive, - RecursiveOption: recursive == recursiveTypeTrue, - Option: recursiveOption, - MustMapReturn: mustMapReturn, - }, - ) - if m, ok := convertedValue.(map[string]interface{}); ok { - return m - } - return nil - default: - return nil - } - } - return dataMap -} - -func getUsedMapOption(option ...MapOption) MapOption { - var usedOption MapOption - if len(option) > 0 { - usedOption = option[0] - } - return usedOption -} - -type doMapConvertForMapOrStructValueInput struct { - IsRoot bool // It returns directly if it is not root and with no recursive converting. - Value interface{} // Current operation value. - RecursiveType recursiveType // The type from top function entry. - RecursiveOption bool // Whether convert recursively for `current` operation. - Option MapOption // Map converting option. - MustMapReturn bool // Must return map instead of Value when empty. -} - -func doMapConvertForMapOrStructValue(in doMapConvertForMapOrStructValueInput) interface{} { - if !in.IsRoot && !in.RecursiveOption { - return in.Value - } - - var reflectValue reflect.Value - if v, ok := in.Value.(reflect.Value); ok { - reflectValue = v - in.Value = v.Interface() - } else { - reflectValue = reflect.ValueOf(in.Value) - } - reflectKind := reflectValue.Kind() - // If it is a pointer, we should find its real data type. - for reflectKind == reflect.Ptr { - reflectValue = reflectValue.Elem() - reflectKind = reflectValue.Kind() - } - switch reflectKind { - case reflect.Map: - var ( - mapIter = reflectValue.MapRange() - dataMap = make(map[string]interface{}) - ) - for mapIter.Next() { - var ( - mapKeyValue = mapIter.Value() - mapValue interface{} - ) - switch { - case mapKeyValue.IsZero(): - if utils.CanCallIsNil(mapKeyValue) && mapKeyValue.IsNil() { - // quick check for nil value. - mapValue = nil - } else { - // in case of: - // exception recovered: reflect: call of reflect.Value.Interface on zero Value - mapValue = reflect.New(mapKeyValue.Type()).Elem().Interface() - } - default: - mapValue = mapKeyValue.Interface() - } - dataMap[String(mapIter.Key().Interface())] = doMapConvertForMapOrStructValue( - doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: mapValue, - RecursiveType: in.RecursiveType, - RecursiveOption: in.RecursiveType == recursiveTypeTrue, - Option: in.Option, - }, - ) - } - return dataMap - - case reflect.Struct: - var dataMap = make(map[string]interface{}) - // Map converting interface check. - if v, ok := in.Value.(localinterface.IMapStrAny); ok { - // Value copy, in case of concurrent safety. - for mapK, mapV := range v.MapStrAny() { - if in.RecursiveOption { - dataMap[mapK] = doMapConvertForMapOrStructValue( - doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: mapV, - RecursiveType: in.RecursiveType, - RecursiveOption: in.RecursiveType == recursiveTypeTrue, - Option: in.Option, - }, - ) - } else { - dataMap[mapK] = mapV - } - } - if len(dataMap) > 0 { - return dataMap - } - } - // Using reflect for converting. - var ( - rtField reflect.StructField - rvField reflect.Value - reflectType = reflectValue.Type() // attribute value type. - mapKey = "" // mapKey may be the tag name or the struct attribute name. - ) - for i := 0; i < reflectValue.NumField(); i++ { - rtField = reflectType.Field(i) - rvField = reflectValue.Field(i) - // Only convert the public attributes. - fieldName := rtField.Name - if !utils.IsLetterUpper(fieldName[0]) { - continue - } - mapKey = "" - fieldTag := rtField.Tag - for _, tag := range in.Option.Tags { - if mapKey = fieldTag.Get(tag); mapKey != "" { - break - } - } - if mapKey == "" { - mapKey = fieldName - } else { - // Support json tag feature: -, omitempty - mapKey = strings.TrimSpace(mapKey) - if mapKey == "-" { - continue - } - array := strings.Split(mapKey, ",") - if len(array) > 1 { - switch strings.TrimSpace(array[1]) { - case "omitempty": - if in.Option.OmitEmpty && empty.IsEmpty(rvField.Interface()) { - continue - } else { - mapKey = strings.TrimSpace(array[0]) - } - default: - mapKey = strings.TrimSpace(array[0]) - } - } - if mapKey == "" { - mapKey = fieldName - } - } - if in.RecursiveOption || rtField.Anonymous { - // Do map converting recursively. - var ( - rvAttrField = rvField - rvAttrKind = rvField.Kind() - ) - if rvAttrKind == reflect.Ptr { - rvAttrField = rvField.Elem() - rvAttrKind = rvAttrField.Kind() - } - switch rvAttrKind { - case reflect.Struct: - // Embedded struct and has no fields, just ignores it. - // Eg: gmeta.Meta - if rvAttrField.Type().NumField() == 0 { - continue - } - var ( - hasNoTag = mapKey == fieldName - // DO NOT use rvAttrField.Interface() here, - // as it might be changed from pointer to struct. - rvInterface = rvField.Interface() - ) - switch { - case hasNoTag && rtField.Anonymous: - // It means this attribute field has no tag. - // Overwrite the attribute with sub-struct attribute fields. - anonymousValue := doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: rvInterface, - RecursiveType: in.RecursiveType, - RecursiveOption: true, - Option: in.Option, - }) - if m, ok := anonymousValue.(map[string]interface{}); ok { - for k, v := range m { - dataMap[k] = v - } - } else { - dataMap[mapKey] = rvInterface - } - - // It means this attribute field has desired tag. - case !hasNoTag && rtField.Anonymous: - dataMap[mapKey] = doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: rvInterface, - RecursiveType: in.RecursiveType, - RecursiveOption: true, - Option: in.Option, - }) - - default: - dataMap[mapKey] = doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: rvInterface, - RecursiveType: in.RecursiveType, - RecursiveOption: in.RecursiveType == recursiveTypeTrue, - Option: in.Option, - }) - } - - // The struct attribute is type of slice. - case reflect.Array, reflect.Slice: - length := rvAttrField.Len() - if length == 0 { - dataMap[mapKey] = rvAttrField.Interface() - break - } - array := make([]interface{}, length) - for arrayIndex := 0; arrayIndex < length; arrayIndex++ { - array[arrayIndex] = doMapConvertForMapOrStructValue( - doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: rvAttrField.Index(arrayIndex).Interface(), - RecursiveType: in.RecursiveType, - RecursiveOption: in.RecursiveType == recursiveTypeTrue, - Option: in.Option, - }, - ) - } - dataMap[mapKey] = array - case reflect.Map: - var ( - mapIter = rvAttrField.MapRange() - nestedMap = make(map[string]interface{}) - ) - for mapIter.Next() { - nestedMap[String(mapIter.Key().Interface())] = doMapConvertForMapOrStructValue( - doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: mapIter.Value().Interface(), - RecursiveType: in.RecursiveType, - RecursiveOption: in.RecursiveType == recursiveTypeTrue, - Option: in.Option, - }, - ) - } - dataMap[mapKey] = nestedMap - default: - if rvField.IsValid() { - dataMap[mapKey] = reflectValue.Field(i).Interface() - } else { - dataMap[mapKey] = nil - } - } - } else { - // No recursive map value converting - if rvField.IsValid() { - dataMap[mapKey] = reflectValue.Field(i).Interface() - } else { - dataMap[mapKey] = nil - } - } - } - if !in.MustMapReturn && len(dataMap) == 0 { - return in.Value - } - return dataMap - - // The given value is type of slice. - case reflect.Array, reflect.Slice: - length := reflectValue.Len() - if length == 0 { - break - } - array := make([]interface{}, reflectValue.Len()) - for i := 0; i < length; i++ { - array[i] = doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: reflectValue.Index(i).Interface(), - RecursiveType: in.RecursiveType, - RecursiveOption: in.RecursiveType == recursiveTypeTrue, - Option: in.Option, - }) - } - return array - - default: - } - return in.Value -} - // MapStrStr converts `value` to map[string]string. // Note that there might be data copy for this map type converting. -func MapStrStr(value interface{}, option ...MapOption) map[string]string { +func MapStrStr(value any, option ...MapOption) map[string]string { if r, ok := value.(map[string]string); ok { return r } @@ -565,7 +68,7 @@ func MapStrStr(value interface{}, option ...MapOption) map[string]string { // MapStrStrDeep converts `value` to map[string]string recursively. // Note that there might be data copy for this map type converting. // Deprecated: used MapStrStr instead. -func MapStrStrDeep(value interface{}, tags ...string) map[string]string { +func MapStrStrDeep(value any, tags ...string) map[string]string { if r, ok := value.(map[string]string); ok { return r } diff --git a/util/gconv/gconv_maptomap.go b/util/gconv/gconv_maptomap.go index bc40a82c0..5986c3322 100644 --- a/util/gconv/gconv_maptomap.go +++ b/util/gconv/gconv_maptomap.go @@ -6,130 +6,9 @@ package gconv -import ( - "reflect" - - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" -) - // MapToMap converts any map type variable `params` to another map type variable `pointer` // using reflect. // See doMapToMap. func MapToMap(params any, pointer any, mapping ...map[string]string) error { return Scan(params, pointer, mapping...) } - -// doMapToMap converts any map type variable `params` to another map type variable `pointer`. -// -// The parameter `params` can be any type of map, like: -// map[string]string, map[string]struct, map[string]*struct, reflect.Value, etc. -// -// The parameter `pointer` should be type of *map, like: -// map[int]string, map[string]struct, map[string]*struct, reflect.Value, etc. -// -// 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(cf *ConvertConfig, params any, pointer any, mapping ...map[string]string) (err error) { - var ( - paramsRv reflect.Value - paramsKind reflect.Kind - keyToAttributeNameMapping map[string]string - ) - if len(mapping) > 0 { - keyToAttributeNameMapping = mapping[0] - } - if v, ok := params.(reflect.Value); ok { - paramsRv = v - } else { - paramsRv = reflect.ValueOf(params) - } - paramsKind = paramsRv.Kind() - if paramsKind == reflect.Ptr { - paramsRv = paramsRv.Elem() - paramsKind = paramsRv.Kind() - } - if paramsKind != reflect.Map { - return doMapToMap(cf, Map(params), pointer, mapping...) - } - // Empty params map, no need continue. - if paramsRv.Len() == 0 { - return nil - } - var pointerRv reflect.Value - if v, ok := pointer.(reflect.Value); ok { - pointerRv = v - } else { - pointerRv = reflect.ValueOf(pointer) - } - pointerKind := pointerRv.Kind() - for pointerKind == reflect.Ptr { - pointerRv = pointerRv.Elem() - pointerKind = pointerRv.Kind() - } - if pointerKind != reflect.Map { - return gerror.NewCodef( - gcode.CodeInvalidParameter, - `destination pointer should be type of *map, but got: %s`, - pointerKind, - ) - } - defer func() { - // Catch the panic, especially the reflection operation panics. - if exception := recover(); exception != nil { - if v, ok := exception.(error); ok && gerror.HasStack(v) { - err = v - } else { - err = gerror.NewCodeSkipf(gcode.CodeInternalPanic, 1, "%+v", exception) - } - } - }() - var ( - paramsKeys = paramsRv.MapKeys() - pointerKeyType = pointerRv.Type().Key() - pointerValueType = pointerRv.Type().Elem() - pointerValueKind = pointerValueType.Kind() - dataMap = reflect.MakeMapWithSize(pointerRv.Type(), len(paramsKeys)) - ) - // Retrieve the true element type of target map. - if pointerValueKind == reflect.Ptr { - pointerValueKind = pointerValueType.Elem().Kind() - } - for _, key := range paramsKeys { - mapValue := reflect.New(pointerValueType).Elem() - switch pointerValueKind { - case reflect.Map, reflect.Struct: - if err = doStruct( - paramsRv.MapIndex(key).Interface(), mapValue, keyToAttributeNameMapping, "", - ); err != nil { - return err - } - default: - mapValue.Set( - reflect.ValueOf( - doConvert( - cf, doConvertInput{ - FromValue: paramsRv.MapIndex(key).Interface(), - ToTypeName: pointerValueType.String(), - ReferValue: mapValue, - Extra: nil, - }), - ), - ) - } - var mapKey = reflect.ValueOf( - doConvert( - cf, - doConvertInput{ - FromValue: key.Interface(), - ToTypeName: pointerKeyType.Name(), - ReferValue: reflect.New(pointerKeyType).Elem().Interface(), - Extra: nil, - }, - ), - ) - dataMap.SetMapIndex(mapKey, mapValue) - } - pointerRv.Set(dataMap) - return nil -} diff --git a/util/gconv/gconv_maptomaps.go b/util/gconv/gconv_maptomaps.go index 330ed40d0..aaf9ae274 100644 --- a/util/gconv/gconv_maptomaps.go +++ b/util/gconv/gconv_maptomaps.go @@ -6,121 +6,8 @@ package gconv -import ( - "reflect" - - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" -) - // MapToMaps converts any slice type variable `params` to another map slice type variable `pointer`. // See doMapToMaps. -func MapToMaps(params interface{}, pointer interface{}, mapping ...map[string]string) error { +func MapToMaps(params any, pointer any, mapping ...map[string]string) error { return Scan(params, pointer, mapping...) } - -// doMapToMaps converts any map type variable `params` to another map slice variable `pointer`. -// -// The parameter `params` can be type of []map, []*map, []struct, []*struct. -// -// The parameter `pointer` should be type of []map, []*map. -// -// The optional parameter `mapping` is used for struct attribute to map key mapping, which makes -// sense only if the item of `params` is type struct. -func doMapToMaps(params interface{}, pointer interface{}, paramKeyToAttrMap ...map[string]string) (err error) { - // Params and its element type check. - var ( - paramsRv reflect.Value - paramsKind reflect.Kind - ) - if v, ok := params.(reflect.Value); ok { - paramsRv = v - } else { - paramsRv = reflect.ValueOf(params) - } - paramsKind = paramsRv.Kind() - if paramsKind == reflect.Ptr { - paramsRv = paramsRv.Elem() - paramsKind = paramsRv.Kind() - } - if paramsKind != reflect.Array && paramsKind != reflect.Slice { - return gerror.NewCode( - gcode.CodeInvalidParameter, - "params should be type of slice, example: []map/[]*map/[]struct/[]*struct", - ) - } - var ( - paramsElem = paramsRv.Type().Elem() - paramsElemKind = paramsElem.Kind() - ) - if paramsElemKind == reflect.Ptr { - paramsElem = paramsElem.Elem() - paramsElemKind = paramsElem.Kind() - } - if paramsElemKind != reflect.Map && - paramsElemKind != reflect.Struct && - paramsElemKind != reflect.Interface { - return gerror.NewCodef( - gcode.CodeInvalidParameter, - "params element should be type of map/*map/struct/*struct, but got: %s", - paramsElemKind, - ) - } - // Empty slice, no need continue. - if paramsRv.Len() == 0 { - return nil - } - // Pointer and its element type check. - var ( - pointerRv = reflect.ValueOf(pointer) - pointerKind = pointerRv.Kind() - ) - for pointerKind == reflect.Ptr { - pointerRv = pointerRv.Elem() - pointerKind = pointerRv.Kind() - } - if pointerKind != reflect.Array && pointerKind != reflect.Slice { - return gerror.NewCode(gcode.CodeInvalidParameter, "pointer should be type of *[]map/*[]*map") - } - var ( - pointerElemType = pointerRv.Type().Elem() - pointerElemKind = pointerElemType.Kind() - ) - if pointerElemKind == reflect.Ptr { - pointerElemKind = pointerElemType.Elem().Kind() - } - if pointerElemKind != reflect.Map { - return gerror.NewCode(gcode.CodeInvalidParameter, "pointer element should be type of map/*map") - } - defer func() { - // Catch the panic, especially the reflection operation panics. - if exception := recover(); exception != nil { - if v, ok := exception.(error); ok && gerror.HasStack(v) { - err = v - } else { - err = gerror.NewCodeSkipf(gcode.CodeInternalPanic, 1, "%+v", exception) - } - } - }() - var ( - pointerSlice = reflect.MakeSlice(pointerRv.Type(), paramsRv.Len(), paramsRv.Len()) - ) - for i := 0; i < paramsRv.Len(); i++ { - var item reflect.Value - if pointerElemType.Kind() == reflect.Ptr { - item = reflect.New(pointerElemType.Elem()) - if err = MapToMap(paramsRv.Index(i).Interface(), item, paramKeyToAttrMap...); err != nil { - return err - } - pointerSlice.Index(i).Set(item) - } else { - item = reflect.New(pointerElemType) - if err = MapToMap(paramsRv.Index(i).Interface(), item, paramKeyToAttrMap...); err != nil { - return err - } - pointerSlice.Index(i).Set(item.Elem()) - } - } - pointerRv.Set(pointerSlice) - return -} diff --git a/util/gconv/gconv_scan.go b/util/gconv/gconv_scan.go index 5ced0c5d8..806438668 100644 --- a/util/gconv/gconv_scan.go +++ b/util/gconv/gconv_scan.go @@ -6,15 +6,6 @@ package gconv -import ( - "reflect" - - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" -) - // Scan automatically checks the type of `pointer` and converts `params` to `pointer`. // It supports various types of parameter conversions, including: // 1. Basic types (int, string, float, etc.) @@ -26,326 +17,5 @@ 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 - } - // Check if dstPointer is nil, which is an invalid parameter - if dstPointer == nil { - return gerror.NewCode( - gcode.CodeInvalidParameter, - `destination pointer should not be nil`, - ) - } - - // Get the reflection type and value of dstPointer - var ( - dstPointerReflectType reflect.Type - dstPointerReflectValue reflect.Value - ) - if v, ok := dstPointer.(reflect.Value); ok { - dstPointerReflectValue = v - dstPointerReflectType = v.Type() - } else { - dstPointerReflectValue = reflect.ValueOf(dstPointer) - // Do not use dstPointerReflectValue.Type() as dstPointerReflectValue might be zero - dstPointerReflectType = reflect.TypeOf(dstPointer) - } - - // Validate the kind of dstPointer - var dstPointerReflectKind = dstPointerReflectType.Kind() - if dstPointerReflectKind != reflect.Ptr { - // If dstPointer is not a pointer, try to get its address - if dstPointerReflectValue.CanAddr() { - dstPointerReflectValue = dstPointerReflectValue.Addr() - dstPointerReflectType = dstPointerReflectValue.Type() - dstPointerReflectKind = dstPointerReflectType.Kind() - } else { - // If dstPointer is not a pointer and cannot be addressed, return an error - return gerror.NewCodef( - gcode.CodeInvalidParameter, - `destination pointer should be type of pointer, but got type: %v`, - dstPointerReflectType, - ) - } - } - - // Get the reflection value of srcValue - var srcValueReflectValue reflect.Value - if v, ok := srcValue.(reflect.Value); ok { - srcValueReflectValue = v - } else { - srcValueReflectValue = reflect.ValueOf(srcValue) - } - - // Get the element type and kind of dstPointer - var ( - dstPointerReflectValueElem = dstPointerReflectValue.Elem() - dstPointerReflectValueElemKind = dstPointerReflectValueElem.Kind() - ) - // Handle multiple level pointers - if dstPointerReflectValueElemKind == reflect.Ptr { - if dstPointerReflectValueElem.IsNil() { - // Create a new value for the pointer dereference - nextLevelPtr := reflect.New(dstPointerReflectValueElem.Type().Elem()) - // Recursively scan into the dereferenced pointer - if err = Scan(srcValueReflectValue, nextLevelPtr, paramKeyToAttrMap...); err == nil { - dstPointerReflectValueElem.Set(nextLevelPtr) - } - return - } - return Scan(srcValueReflectValue, dstPointerReflectValueElem, paramKeyToAttrMap...) - } - - // Check if srcValue and dstPointer are the same type, in which case direct assignment can be performed - if ok := doConvertWithTypeCheck(srcValueReflectValue, dstPointerReflectValueElem); ok { - return nil - } - - // Handle different destination types - switch dstPointerReflectValueElemKind { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - // Convert to int type - dstPointerReflectValueElem.SetInt(Int64(srcValue)) - return nil - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - // Convert to uint type - dstPointerReflectValueElem.SetUint(Uint64(srcValue)) - return nil - - case reflect.Float32, reflect.Float64: - // Convert to float type - dstPointerReflectValueElem.SetFloat(Float64(srcValue)) - return nil - - case reflect.String: - // Convert to string type - dstPointerReflectValueElem.SetString(String(srcValue)) - return nil - - case reflect.Bool: - // Convert to bool type - dstPointerReflectValueElem.SetBool(Bool(srcValue)) - return nil - - case reflect.Slice: - // Handle slice type conversion - var ( - dstElemType = dstPointerReflectValueElem.Type().Elem() - dstElemKind = dstElemType.Kind() - ) - // The slice element might be a pointer type - if dstElemKind == reflect.Ptr { - dstElemType = dstElemType.Elem() - dstElemKind = dstElemType.Kind() - } - // Special handling for struct or map slice elements - if dstElemKind == reflect.Struct || dstElemKind == reflect.Map { - return doScanForComplicatedTypes(cf, srcValue, dstPointer, dstPointerReflectType, paramKeyToAttrMap...) - } - // Handle basic type slice conversions - var srcValueReflectValueKind = srcValueReflectValue.Kind() - if srcValueReflectValueKind == reflect.Slice || srcValueReflectValueKind == reflect.Array { - var ( - srcLen = srcValueReflectValue.Len() - newSlice = reflect.MakeSlice(dstPointerReflectValueElem.Type(), srcLen, srcLen) - ) - for i := 0; i < srcLen; i++ { - srcElem := srcValueReflectValue.Index(i).Interface() - switch dstElemType.Kind() { - case reflect.String: - newSlice.Index(i).SetString(String(srcElem)) - case reflect.Int: - newSlice.Index(i).SetInt(Int64(srcElem)) - case reflect.Int64: - newSlice.Index(i).SetInt(Int64(srcElem)) - case reflect.Float64: - newSlice.Index(i).SetFloat(Float64(srcElem)) - case reflect.Bool: - newSlice.Index(i).SetBool(Bool(srcElem)) - default: - return Scan( - srcElem, newSlice.Index(i).Addr().Interface(), paramKeyToAttrMap..., - ) - } - } - dstPointerReflectValueElem.Set(newSlice) - return nil - } - return doScanForComplicatedTypes(cf, srcValue, dstPointer, dstPointerReflectType, paramKeyToAttrMap...) - - default: - // Handle complex types (structs, maps, etc.) - return doScanForComplicatedTypes(cf, srcValue, dstPointer, dstPointerReflectType, paramKeyToAttrMap...) - } -} - -// doScanForComplicatedTypes handles the scanning of complex data types. -// It supports converting between maps, structs, and slices of these types. -// The function first attempts JSON conversion, then falls back to specific type handling. -// -// It supports `pointer` in type of `*map/*[]map/*[]*map/*struct/**struct/*[]struct/*[]*struct` for converting. -// -// Parameters: -// - srcValue: The source value to convert from -// - dstPointer: The destination pointer to convert to -// - 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, -) error { - // Try JSON conversion first - ok, err := doConvertWithJsonCheck(srcValue, dstPointer) - if err != nil { - return err - } - if ok { - return nil - } - - // Handle specific type conversions - var ( - dstPointerReflectTypeElem = dstPointerReflectType.Elem() - dstPointerReflectTypeElemKind = dstPointerReflectTypeElem.Kind() - keyToAttributeNameMapping map[string]string - ) - if len(paramKeyToAttrMap) > 0 { - keyToAttributeNameMapping = paramKeyToAttrMap[0] - } - - // Handle different destination types - switch dstPointerReflectTypeElemKind { - case reflect.Map: - // Convert map to map - return doMapToMap(cf, srcValue, dstPointer, paramKeyToAttrMap...) - - case reflect.Array, reflect.Slice: - var ( - sliceElem = dstPointerReflectTypeElem.Elem() - sliceElemKind = sliceElem.Kind() - ) - // Handle pointer elements - for sliceElemKind == reflect.Ptr { - sliceElem = sliceElem.Elem() - sliceElemKind = sliceElem.Kind() - } - if sliceElemKind == reflect.Map { - // Convert to slice of maps - return doMapToMaps(srcValue, dstPointer, paramKeyToAttrMap...) - } - // Convert to slice of structs - return doStructs(srcValue, dstPointer, keyToAttributeNameMapping, "") - - default: - // Convert to single struct - return doStruct(srcValue, dstPointer, keyToAttributeNameMapping, "") - } -} - -// doConvertWithTypeCheck supports `pointer` in type of `*map/*[]map/*[]*map/*struct/**struct/*[]struct/*[]*struct` -// for converting. -func doConvertWithTypeCheck(srcValueReflectValue, dstPointerReflectValueElem reflect.Value) (ok bool) { - if !dstPointerReflectValueElem.IsValid() || !srcValueReflectValue.IsValid() { - return false - } - switch { - // Examples: - // UploadFile => UploadFile - // []UploadFile => []UploadFile - // *UploadFile => *UploadFile - // *[]UploadFile => *[]UploadFile - // map[int][int] => map[int][int] - // []map[int][int] => []map[int][int] - // *[]map[int][int] => *[]map[int][int] - case dstPointerReflectValueElem.Type() == srcValueReflectValue.Type(): - dstPointerReflectValueElem.Set(srcValueReflectValue) - return true - - // Examples: - // UploadFile => *UploadFile - // []UploadFile => *[]UploadFile - // map[int][int] => *map[int][int] - // []map[int][int] => *[]map[int][int] - case dstPointerReflectValueElem.Kind() == reflect.Ptr && - dstPointerReflectValueElem.Elem().IsValid() && - dstPointerReflectValueElem.Elem().Type() == srcValueReflectValue.Type(): - dstPointerReflectValueElem.Elem().Set(srcValueReflectValue) - return true - - // Examples: - // *UploadFile => UploadFile - // *[]UploadFile => []UploadFile - // *map[int][int] => map[int][int] - // *[]map[int][int] => []map[int][int] - case srcValueReflectValue.Kind() == reflect.Ptr && - srcValueReflectValue.Elem().IsValid() && - dstPointerReflectValueElem.Type() == srcValueReflectValue.Elem().Type(): - dstPointerReflectValueElem.Set(srcValueReflectValue.Elem()) - return true - - default: - return false - } -} - -// doConvertWithJsonCheck attempts to convert the source value to the destination -// using JSON marshaling and unmarshaling. This is particularly useful for complex -// types that can be represented as JSON. -// -// Parameters: -// - srcValue: The source value to convert from -// - dstPointer: The destination pointer to convert to -// -// Returns: -// - bool: true if JSON conversion was successful -// - error: any error that occurred during conversion -func doConvertWithJsonCheck(srcValue any, dstPointer any) (ok bool, err error) { - switch valueResult := srcValue.(type) { - case []byte: - if json.Valid(valueResult) { - if dstPointerReflectType, ok := dstPointer.(reflect.Value); ok { - if dstPointerReflectType.Kind() == reflect.Ptr { - if dstPointerReflectType.IsNil() { - return false, nil - } - return true, json.UnmarshalUseNumber(valueResult, dstPointerReflectType.Interface()) - } else if dstPointerReflectType.CanAddr() { - return true, json.UnmarshalUseNumber(valueResult, dstPointerReflectType.Addr().Interface()) - } - } else { - return true, json.UnmarshalUseNumber(valueResult, dstPointer) - } - } - - case string: - if valueBytes := []byte(valueResult); json.Valid(valueBytes) { - if dstPointerReflectType, ok := dstPointer.(reflect.Value); ok { - if dstPointerReflectType.Kind() == reflect.Ptr { - if dstPointerReflectType.IsNil() { - return false, nil - } - return true, json.UnmarshalUseNumber(valueBytes, dstPointerReflectType.Interface()) - } else if dstPointerReflectType.CanAddr() { - return true, json.UnmarshalUseNumber(valueBytes, dstPointerReflectType.Addr().Interface()) - } - } else { - return true, json.UnmarshalUseNumber(valueBytes, dstPointer) - } - } - - default: - // The `params` might be struct that implements interface function Interface, eg: gvar.Var. - if v, ok := srcValue.(localinterface.IInterface); ok { - return doConvertWithJsonCheck(v.Interface(), dstPointer) - } - } - return false, nil + return defaultConverter.Scan(srcValue, dstPointer, paramKeyToAttrMap...) } diff --git a/util/gconv/gconv_struct.go b/util/gconv/gconv_struct.go index e47ee395f..d3fe9570c 100644 --- a/util/gconv/gconv_struct.go +++ b/util/gconv/gconv_struct.go @@ -6,19 +6,6 @@ package gconv -import ( - "reflect" - "strings" - - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/internal/empty" - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/internal/utils" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" - "github.com/gogf/gf/v2/util/gconv/internal/structcache" -) - // Struct maps the params key-value pairs to the corresponding struct object's attributes. // The third parameter `mapping` is unnecessary, indicating the mapping rules between the // custom key name and the attribute name(case-sensitive). @@ -40,625 +27,5 @@ func Struct(params any, pointer any, paramKeyToAttrMap ...map[string]string) (er // 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 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 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. - return nil - } - if pointer == nil { - return gerror.NewCode(gcode.CodeInvalidParameter, "object pointer cannot be nil") - } - - // JSON content converting. - ok, err := doConvertWithJsonCheck(params, pointer) - if err != nil { - return err - } - if ok { - return nil - } - - defer func() { - // Catch the panic, especially the reflection operation panics. - if exception := recover(); exception != nil { - if v, ok := exception.(error); ok && gerror.HasStack(v) { - err = v - } else { - err = gerror.NewCodeSkipf(gcode.CodeInternalPanic, 1, "%+v", exception) - } - } - }() - - var ( - paramsReflectValue 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. - ) - if v, ok := params.(reflect.Value); ok { - paramsReflectValue = v - } else { - paramsReflectValue = reflect.ValueOf(params) - } - paramsInterface = paramsReflectValue.Interface() - if v, ok := pointer.(reflect.Value); ok { - pointerReflectValue = v - pointerElemReflectValue = v - } else { - pointerReflectValue = reflect.ValueOf(pointer) - pointerReflectKind = pointerReflectValue.Kind() - if pointerReflectKind != reflect.Ptr { - return gerror.NewCodef( - gcode.CodeInvalidParameter, - "destination pointer should be type of '*struct', but got '%v'", - pointerReflectKind, - ) - } - // Using IsNil on reflect.Ptr variable is OK. - if !pointerReflectValue.IsValid() || pointerReflectValue.IsNil() { - return gerror.NewCode( - gcode.CodeInvalidParameter, - "destination pointer cannot be nil", - ) - } - pointerElemReflectValue = pointerReflectValue.Elem() - } - - // If `params` and `pointer` are the same type, the do directly assignment. - // For performance enhancement purpose. - if ok = doConvertWithTypeCheck(paramsReflectValue, pointerElemReflectValue); ok { - return nil - } - - // custom convert. - if ok, err = cf.callCustomConverter(paramsReflectValue, pointerReflectValue); ok { - return err - } - - // Normal unmarshalling interfaces checks. - if ok, err = bindVarToReflectValueWithInterfaceCheck(pointerReflectValue, paramsInterface); ok { - return err - } - - // It automatically creates struct object if necessary. - // 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()) - 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().(localinterface.IUnmarshalValue); ok { - // return v.UnmarshalValue(params) - // } - // Note that it's `pointerElemReflectValue` here not `pointerReflectValue`. - if ok, err = bindVarToReflectValueWithInterfaceCheck(pointerElemReflectValue, paramsInterface); ok { - return err - } - // Retrieve its element, may be struct at last. - pointerElemReflectValue = pointerElemReflectValue.Elem() - } - paramsMap, ok := paramsInterface.(map[string]any) - if !ok { - // 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]any" failed`, - params, - ) - } - } - // Nothing to be done as the parameters are empty. - if len(paramsMap) == 0 { - return nil - } - // Get struct info from cache or parse struct and cache the struct info. - cachedStructInfo := cf.internalConvertConfig.GetCachedStructInfo( - pointerElemReflectValue.Type(), priorityTag, - ) - // Nothing to be converted. - if cachedStructInfo == nil { - return nil - } - // For the structure types of 0 tagOrFiledNameToFieldInfoMap, - // they also need to be cached to prevent invalid logic - if cachedStructInfo.HasNoFields() { - return nil - } - var ( - // Indicates that those values have been used and cannot be reused. - usedParamsKeyOrTagNameMap = structcache.GetUsedParamsKeyOrTagNameMapFromPool() - cachedFieldInfo *structcache.CachedFieldInfo - paramsValue any - ) - defer structcache.PutUsedParamsKeyOrTagNameMapToPool(usedParamsKeyOrTagNameMap) - - // Firstly, search according to custom mapping rules. - // If a possible direct assignment is found, reduce the number of subsequent map searches. - for paramKey, fieldName := range paramKeyToAttrMap { - paramsValue, ok = paramsMap[paramKey] - if !ok { - continue - } - cachedFieldInfo = cachedStructInfo.GetFieldInfo(fieldName) - if cachedFieldInfo != nil { - fieldValue := cachedFieldInfo.GetFieldReflectValueFrom(pointerElemReflectValue) - if err = bindVarToStructField( - cf, - cachedFieldInfo, - fieldValue, - paramsValue, - paramKeyToAttrMap, - ); err != nil { - return err - } - if len(cachedFieldInfo.OtherSameNameField) > 0 { - if err = setOtherSameNameField( - cf, cachedFieldInfo, paramsValue, pointerReflectValue, paramKeyToAttrMap, - ); err != nil { - return err - } - } - usedParamsKeyOrTagNameMap[paramKey] = struct{}{} - } - } - // Already done converting for given `paramsMap`. - if len(usedParamsKeyOrTagNameMap) == len(paramsMap) { - return nil - } - return bindStructWithLoopFieldInfos( - cf, paramsMap, pointerElemReflectValue, paramKeyToAttrMap, usedParamsKeyOrTagNameMap, cachedStructInfo, - ) -} - -func setOtherSameNameField( - cf *ConvertConfig, - cachedFieldInfo *structcache.CachedFieldInfo, - srcValue any, - structValue reflect.Value, - paramKeyToAttrMap map[string]string, -) (err error) { - // loop the same field name of all sub attributes. - for _, otherFieldInfo := range cachedFieldInfo.OtherSameNameField { - fieldValue := cachedFieldInfo.GetOtherFieldReflectValueFrom(structValue, otherFieldInfo.FieldIndexes) - if err = bindVarToStructField(cf, otherFieldInfo, fieldValue, srcValue, paramKeyToAttrMap); err != nil { - return err - } - } - return nil -} - -func bindStructWithLoopFieldInfos( - cf *ConvertConfig, - paramsMap map[string]any, - structValue reflect.Value, - paramKeyToAttrMap map[string]string, - usedParamsKeyOrTagNameMap map[string]struct{}, - cachedStructInfo *structcache.CachedStructInfo, -) (err error) { - var ( - cachedFieldInfo *structcache.CachedFieldInfo - fuzzLastKey string - fieldValue reflect.Value - paramKey string - paramValue any - matched bool - ok bool - ) - for _, cachedFieldInfo = range cachedStructInfo.GetFieldConvertInfos() { - for _, fieldTag := range cachedFieldInfo.PriorityTagAndFieldName { - if paramValue, ok = paramsMap[fieldTag]; !ok { - continue - } - fieldValue = cachedFieldInfo.GetFieldReflectValueFrom(structValue) - if err = bindVarToStructField( - cf, cachedFieldInfo, fieldValue, paramValue, paramKeyToAttrMap, - ); err != nil { - return err - } - // handle same field name in nested struct. - if len(cachedFieldInfo.OtherSameNameField) > 0 { - if err = setOtherSameNameField( - cf, cachedFieldInfo, paramValue, structValue, paramKeyToAttrMap, - ); err != nil { - return err - } - } - usedParamsKeyOrTagNameMap[fieldTag] = struct{}{} - matched = true - break - } - if matched { - matched = false - continue - } - - fuzzLastKey = cachedFieldInfo.LastFuzzyKey.Load().(string) - if paramValue, ok = paramsMap[fuzzLastKey]; !ok { - paramKey, paramValue = fuzzyMatchingFieldName( - cachedFieldInfo.RemoveSymbolsFieldName, paramsMap, usedParamsKeyOrTagNameMap, - ) - ok = paramKey != "" - cachedFieldInfo.LastFuzzyKey.Store(paramKey) - } - if ok { - fieldValue = cachedFieldInfo.GetFieldReflectValueFrom(structValue) - if paramValue != nil { - if err = bindVarToStructField( - cf, cachedFieldInfo, fieldValue, paramValue, paramKeyToAttrMap, - ); err != nil { - return err - } - // handle same field name in nested struct. - if len(cachedFieldInfo.OtherSameNameField) > 0 { - if err = setOtherSameNameField( - cf, cachedFieldInfo, paramValue, structValue, paramKeyToAttrMap, - ); err != nil { - return err - } - } - } - usedParamsKeyOrTagNameMap[paramKey] = struct{}{} - } - } - return nil -} - -// fuzzy matching rule: -// to match field name and param key in case-insensitive and without symbols. -func fuzzyMatchingFieldName( - fieldName string, - paramsMap map[string]any, - usedParamsKeyMap map[string]struct{}, -) (string, any) { - for paramKey, paramValue := range paramsMap { - if _, ok := usedParamsKeyMap[paramKey]; ok { - continue - } - removeParamKeyUnderline := utils.RemoveSymbols(paramKey) - if strings.EqualFold(fieldName, removeParamKeyUnderline) { - return paramKey, paramValue - } - } - return "", nil -} - -// bindVarToStructField sets value to struct object attribute by name. -// each value to attribute converting comes into in this function. -func bindVarToStructField( - cf *ConvertConfig, - cachedFieldInfo *structcache.CachedFieldInfo, - fieldValue reflect.Value, - srcValue any, - paramKeyToAttrMap map[string]string, -) (err error) { - if !fieldValue.IsValid() { - return nil - } - // CanSet checks whether attribute is public accessible. - if !fieldValue.CanSet() { - return nil - } - defer func() { - if exception := recover(); exception != nil { - if err = bindVarToReflectValue(cf, fieldValue, srcValue, paramKeyToAttrMap); err != nil { - err = gerror.Wrapf(err, `error binding srcValue to attribute "%s"`, cachedFieldInfo.FieldName()) - } - } - }() - // Directly converting. - if empty.IsNil(srcValue) { - fieldValue.Set(reflect.Zero(fieldValue.Type())) - return nil - } - // Try to call custom converter. - // Issue: https://github.com/gogf/gf/issues/3099 - var ( - customConverterInput reflect.Value - ok bool - ) - if cachedFieldInfo.IsCustomConvert { - if customConverterInput, ok = srcValue.(reflect.Value); !ok { - customConverterInput = reflect.ValueOf(srcValue) - } - if ok, err = cf.callCustomConverter(customConverterInput, fieldValue); ok || err != nil { - return - } - } - if cachedFieldInfo.IsCommonInterface { - if ok, err = bindVarToReflectValueWithInterfaceCheck(fieldValue, srcValue); ok || err != nil { - return - } - } - // Common types use fast assignment logic - if cachedFieldInfo.ConvertFunc != nil { - return cachedFieldInfo.ConvertFunc(srcValue, 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 any) (bool, error) { - var pointer any - if reflectValue.Kind() != reflect.Ptr && reflectValue.CanAddr() { - reflectValueAddr := reflectValue.Addr() - if reflectValueAddr.IsNil() || !reflectValueAddr.IsValid() { - return false, nil - } - // Not a pointer, but can token address, that makes it can be unmarshalled. - pointer = reflectValue.Addr().Interface() - } else { - if reflectValue.IsNil() || !reflectValue.IsValid() { - return false, nil - } - pointer = reflectValue.Interface() - } - // UnmarshalValue. - if v, ok := pointer.(localinterface.IUnmarshalValue); ok { - return ok, v.UnmarshalValue(value) - } - // UnmarshalText. - if v, ok := pointer.(localinterface.IUnmarshalText); ok { - var valueBytes []byte - if b, ok := value.([]byte); ok { - valueBytes = b - } else if s, ok := value.(string); ok { - valueBytes = []byte(s) - } else if f, ok := value.(localinterface.IString); ok { - valueBytes = []byte(f.String()) - } - if len(valueBytes) > 0 { - return ok, v.UnmarshalText(valueBytes) - } - } - // UnmarshalJSON. - if v, ok := pointer.(localinterface.IUnmarshalJSON); ok { - var valueBytes []byte - if b, ok := value.([]byte); ok { - valueBytes = b - } else if s, ok := value.(string); ok { - valueBytes = []byte(s) - } else if f, ok := value.(localinterface.IString); ok { - valueBytes = []byte(f.String()) - } - - if len(valueBytes) > 0 { - // If it is not a valid JSON string, it then adds char `"` on its both sides to make it is. - if !json.Valid(valueBytes) { - newValueBytes := make([]byte, len(valueBytes)+2) - newValueBytes[0] = '"' - newValueBytes[len(newValueBytes)-1] = '"' - copy(newValueBytes[1:], valueBytes) - valueBytes = newValueBytes - } - return ok, v.UnmarshalJSON(valueBytes) - } - } - if v, ok := pointer.(localinterface.ISet); ok { - v.Set(value) - return ok, nil - } - return false, nil -} - -// bindVarToReflectValue sets `value` to reflect value object `structFieldValue`. -func bindVarToReflectValue( - cf *ConvertConfig, structFieldValue reflect.Value, value any, paramKeyToAttrMap map[string]string, -) (err error) { - // JSON content converting. - ok, err := doConvertWithJsonCheck(value, structFieldValue) - if err != nil { - return err - } - if ok { - return nil - } - - kind := structFieldValue.Kind() - // Converting using `Set` interface implements, for some types. - switch kind { - case reflect.Slice, reflect.Array, reflect.Ptr, reflect.Interface: - if !structFieldValue.IsNil() { - if v, ok := structFieldValue.Interface().(localinterface.ISet); ok { - v.Set(value) - return nil - } - } - } - - // Converting using reflection by kind. - switch kind { - case reflect.Map: - return doMapToMap(cf, value, structFieldValue, paramKeyToAttrMap) - - case reflect.Struct: - // Recursively converting for struct attribute. - if err = doStruct(value, structFieldValue, nil, ""); err != nil { - // Note there's reflect conversion mechanism here. - structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type())) - } - - // Note that the slice element might be type of struct, - // so it uses Struct function doing the converting internally. - case reflect.Slice, reflect.Array: - var ( - reflectArray reflect.Value - reflectValue = reflect.ValueOf(value) - ) - if reflectValue.Kind() == reflect.Slice || reflectValue.Kind() == reflect.Array { - reflectArray = reflect.MakeSlice(structFieldValue.Type(), reflectValue.Len(), reflectValue.Len()) - if reflectValue.Len() > 0 { - var ( - elemType = reflectArray.Index(0).Type() - elemTypeName string - converted bool - ) - for i := 0; i < reflectValue.Len(); i++ { - converted = false - elemTypeName = elemType.Name() - if elemTypeName == "" { - elemTypeName = elemType.String() - } - var elem reflect.Value - if elemType.Kind() == reflect.Ptr { - elem = reflect.New(elemType.Elem()).Elem() - } else { - elem = reflect.New(elemType).Elem() - } - if elem.Kind() == reflect.Struct { - if err = doStruct(reflectValue.Index(i).Interface(), elem, nil, ""); err == nil { - converted = true - } - } - if !converted { - 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. - elem = elem.Addr() - } - reflectArray.Index(i).Set(elem) - } - } - } else { - var ( - elem reflect.Value - elemType = structFieldValue.Type().Elem() - elemTypeName = elemType.Name() - converted bool - ) - switch reflectValue.Kind() { - case reflect.String: - // Value is empty string. - if reflectValue.IsZero() { - var elemKind = elemType.Kind() - // Try to find the original type kind of the slice element. - if elemKind == reflect.Ptr { - elemKind = elemType.Elem().Kind() - } - switch elemKind { - case reflect.String: - // Empty string cannot be assigned to string slice. - return nil - } - } - } - if elemTypeName == "" { - elemTypeName = elemType.String() - } - if elemType.Kind() == reflect.Ptr { - elem = reflect.New(elemType.Elem()).Elem() - } else { - elem = reflect.New(elemType).Elem() - } - if elem.Kind() == reflect.Struct { - if err = doStruct(value, elem, nil, ""); err == nil { - converted = true - } - } - if !converted { - 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. - elem = elem.Addr() - } - reflectArray = reflect.MakeSlice(structFieldValue.Type(), 1, 1) - reflectArray.Index(0).Set(elem) - } - structFieldValue.Set(reflectArray) - - case reflect.Ptr: - if structFieldValue.IsNil() || structFieldValue.IsZero() { - // Nil or empty pointer, it creates a new one. - item := reflect.New(structFieldValue.Type().Elem()) - if ok, err = bindVarToReflectValueWithInterfaceCheck(item, value); ok { - structFieldValue.Set(item) - return err - } - elem := item.Elem() - if err = bindVarToReflectValue(cf, elem, value, paramKeyToAttrMap); err == nil { - structFieldValue.Set(elem.Addr()) - } - } else { - // Not empty pointer, it assigns values to it. - 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((*any)(nil))) - } else { - // Note there's reflect conversion mechanism here. - structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type())) - } - - default: - defer func() { - if exception := recover(); exception != nil { - err = gerror.NewCodef( - gcode.CodeInternalPanic, - `cannot convert value "%+v" to type "%s":%+v`, - value, - structFieldValue.Type().String(), - exception, - ) - } - }() - // It here uses reflect converting `value` to type of the attribute and assigns - // the result value to the attribute. It might fail and panic if the usual Go - // conversion rules do not allow conversion. - structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type())) - } - return nil + return defaultConverter.Struct(params, pointer, nil, priorityTag) } diff --git a/util/gconv/gconv_structs.go b/util/gconv/gconv_structs.go index 867fcdd2b..238b801e2 100644 --- a/util/gconv/gconv_structs.go +++ b/util/gconv/gconv_structs.go @@ -6,128 +6,20 @@ package gconv -import ( - "reflect" - - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" -) - // Structs converts any slice to given struct slice. // Also see Scan, Struct. -func Structs(params interface{}, pointer interface{}, paramKeyToAttrMap ...map[string]string) (err error) { +func Structs(params any, pointer any, paramKeyToAttrMap ...map[string]string) (err error) { return Scan(params, pointer, paramKeyToAttrMap...) } // SliceStruct is alias of Structs. -func SliceStruct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) { +func SliceStruct(params any, pointer any, mapping ...map[string]string) (err error) { return Structs(params, pointer, mapping...) } // StructsTag acts as Structs 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 StructsTag(params interface{}, pointer interface{}, priorityTag string) (err error) { - return doStructs(params, pointer, nil, priorityTag) -} - -// doStructs converts any slice to given struct slice. -// -// It automatically checks and converts json string to []map if `params` is string/[]byte. -// -// The parameter `pointer` should be type of pointer to slice of struct. -// Note that if `pointer` is a pointer to another pointer of type of slice of struct, -// it will create the struct/pointer internally. -func doStructs( - params interface{}, pointer interface{}, paramKeyToAttrMap map[string]string, priorityTag string, -) (err error) { - defer func() { - // Catch the panic, especially the reflection operation panics. - if exception := recover(); exception != nil { - if v, ok := exception.(error); ok && gerror.HasStack(v) { - err = v - } else { - err = gerror.NewCodeSkipf(gcode.CodeInternalPanic, 1, "%+v", exception) - } - } - }() - - // Pointer type check. - pointerRv, ok := pointer.(reflect.Value) - if !ok { - pointerRv = reflect.ValueOf(pointer) - if kind := pointerRv.Kind(); kind != reflect.Ptr { - return gerror.NewCodef( - gcode.CodeInvalidParameter, - "pointer should be type of pointer, but got: %v", kind, - ) - } - } - // Converting `params` to map slice. - var ( - paramsList []interface{} - paramsRv = reflect.ValueOf(params) - paramsKind = paramsRv.Kind() - ) - for paramsKind == reflect.Ptr { - paramsRv = paramsRv.Elem() - paramsKind = paramsRv.Kind() - } - switch paramsKind { - case reflect.Slice, reflect.Array: - paramsList = make([]interface{}, paramsRv.Len()) - for i := 0; i < paramsRv.Len(); i++ { - paramsList[i] = paramsRv.Index(i).Interface() - } - default: - var paramsMaps = Maps(params) - paramsList = make([]interface{}, len(paramsMaps)) - for i := 0; i < len(paramsMaps); i++ { - paramsList[i] = paramsMaps[i] - } - } - // If `params` is an empty slice, no conversion. - if len(paramsList) == 0 { - return nil - } - var ( - reflectElemArray = reflect.MakeSlice(pointerRv.Type().Elem(), len(paramsList), len(paramsList)) - itemType = reflectElemArray.Index(0).Type() - itemTypeKind = itemType.Kind() - pointerRvElem = pointerRv.Elem() - pointerRvLength = pointerRvElem.Len() - ) - if itemTypeKind == reflect.Ptr { - // Pointer element. - for i := 0; i < len(paramsList); i++ { - var tempReflectValue reflect.Value - if i < pointerRvLength { - // Might be nil. - tempReflectValue = pointerRvElem.Index(i).Elem() - } - if !tempReflectValue.IsValid() { - tempReflectValue = reflect.New(itemType.Elem()).Elem() - } - if err = doStruct(paramsList[i], tempReflectValue, paramKeyToAttrMap, priorityTag); err != nil { - return err - } - reflectElemArray.Index(i).Set(tempReflectValue.Addr()) - } - } else { - // Struct element. - for i := 0; i < len(paramsList); i++ { - var tempReflectValue reflect.Value - if i < pointerRvLength { - tempReflectValue = pointerRvElem.Index(i) - } else { - tempReflectValue = reflect.New(itemType).Elem() - } - if err = doStruct(paramsList[i], tempReflectValue, paramKeyToAttrMap, priorityTag); err != nil { - return err - } - reflectElemArray.Index(i).Set(tempReflectValue) - } - } - pointerRv.Elem().Set(reflectElemArray) - return nil +func StructsTag(params any, pointer any, priorityTag string) (err error) { + return defaultConverter.Structs(params, pointer, nil, priorityTag) } diff --git a/util/gconv/gconv_time.go b/util/gconv/gconv_time.go index 202b5b557..1102e0e87 100644 --- a/util/gconv/gconv_time.go +++ b/util/gconv/gconv_time.go @@ -9,40 +9,21 @@ package gconv import ( "time" - "github.com/gogf/gf/v2/internal/empty" - "github.com/gogf/gf/v2/internal/utils" "github.com/gogf/gf/v2/os/gtime" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" ) // Time converts `any` to time.Time. -func Time(any interface{}, format ...string) time.Time { - // It's already this type. - if len(format) == 0 { - if v, ok := any.(time.Time); ok { - return v - } - } - if t := GTime(any, format...); t != nil { - return t.Time - } - return time.Time{} +func Time(any any, format ...string) time.Time { + t, _ := defaultConverter.Time(any, format...) + return t } // Duration converts `any` to time.Duration. // If `any` is string, then it uses time.ParseDuration to convert it. // If `any` is numeric, then it converts `any` as nanoseconds. -func Duration(any interface{}) time.Duration { - // It's already this type. - if v, ok := any.(time.Duration); ok { - return v - } - s := String(any) - if !utils.IsNumeric(s) { - d, _ := gtime.ParseDuration(s) - return d - } - return time.Duration(Int64(any)) +func Duration(any any) time.Duration { + d, _ := defaultConverter.Duration(any) + return d } // GTime converts `any` to *gtime.Time. @@ -50,43 +31,7 @@ func Duration(any interface{}) time.Duration { // It returns the converted value that matched the first format of the formats slice. // If no `format` given, it converts `any` using gtime.NewFromTimeStamp if `any` is numeric, // or using gtime.StrToTime if `any` is string. -func GTime(any interface{}, format ...string) *gtime.Time { - if empty.IsNil(any) { - return nil - } - if v, ok := any.(localinterface.IGTime); ok { - return v.GTime(format...) - } - // It's already this type. - if len(format) == 0 { - if v, ok := any.(*gtime.Time); ok { - return v - } - if t, ok := any.(time.Time); ok { - return gtime.New(t) - } - if t, ok := any.(*time.Time); ok { - return gtime.New(t) - } - } - s := String(any) - if len(s) == 0 { - return gtime.New() - } - // Priority conversion using given format. - if len(format) > 0 { - for _, item := range format { - t, err := gtime.StrToTimeFormat(s, item) - if t != nil && err == nil { - return t - } - } - return nil - } - if utils.IsNumeric(s) { - return gtime.NewFromTimeStamp(Int64(s)) - } else { - t, _ := gtime.StrToTime(s) - return t - } +func GTime(any any, format ...string) *gtime.Time { + t, _ := defaultConverter.GTime(any, format...) + return t } diff --git a/util/gconv/gconv_uint.go b/util/gconv/gconv_uint.go index 75e876f51..b7c9f66fd 100644 --- a/util/gconv/gconv_uint.go +++ b/util/gconv/gconv_uint.go @@ -6,181 +6,32 @@ package gconv -import ( - "math" - "reflect" - "strconv" - - "github.com/gogf/gf/v2/encoding/gbinary" - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/internal/empty" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" -) - // Uint converts `any` to uint. func Uint(any any) uint { - v, _ := doUint(any) + v, _ := defaultConverter.Uint(any) return v } -func doUint(any any) (uint, error) { - if empty.IsNil(any) { - return 0, nil - } - if v, ok := any.(uint); ok { - return v, nil - } - v, err := doUint64(any) - return uint(v), err -} - // Uint8 converts `any` to uint8. func Uint8(any any) uint8 { - v, _ := doUint8(any) + v, _ := defaultConverter.Uint8(any) return v } -func doUint8(any any) (uint8, error) { - if empty.IsNil(any) { - return 0, nil - } - if v, ok := any.(uint8); ok { - return v, nil - } - v, err := doUint64(any) - return uint8(v), err -} - // Uint16 converts `any` to uint16. func Uint16(any any) uint16 { - v, _ := doUint16(any) + v, _ := defaultConverter.Uint16(any) return v } -func doUint16(any any) (uint16, error) { - if empty.IsNil(any) { - return 0, nil - } - if v, ok := any.(uint16); ok { - return v, nil - } - v, err := doUint64(any) - return uint16(v), err -} - // Uint32 converts `any` to uint32. func Uint32(any any) uint32 { - v, _ := doUint32(any) + v, _ := defaultConverter.Uint32(any) return v } -func doUint32(any any) (uint32, error) { - if empty.IsNil(any) { - return 0, nil - } - if v, ok := any.(uint32); ok { - return v, nil - } - v, err := doUint64(any) - return uint32(v), err -} - // Uint64 converts `any` to uint64. func Uint64(any any) uint64 { - v, _ := doUint64(any) + v, _ := defaultConverter.Uint64(any) return v } - -func doUint64(any any) (uint64, error) { - if empty.IsNil(any) { - return 0, nil - } - if v, ok := any.(uint64); ok { - return v, nil - } - rv := reflect.ValueOf(any) - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - val := rv.Int() - if val < 0 { - return uint64(val), gerror.NewCodef( - gcode.CodeInvalidParameter, - `cannot convert negative value "%d" to uint64`, - val, - ) - } - return uint64(val), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return rv.Uint(), nil - case reflect.Uintptr: - return rv.Uint(), nil - case reflect.Float32, reflect.Float64: - val := rv.Float() - if val < 0 { - return uint64(val), gerror.NewCodef( - gcode.CodeInvalidParameter, - `cannot convert negative value "%f" to uint64`, - val, - ) - } - return uint64(val), nil - case reflect.Bool: - if rv.Bool() { - return 1, nil - } - return 0, nil - case reflect.Ptr: - if rv.IsNil() { - return 0, nil - } - if f, ok := any.(localinterface.IUint64); ok { - return f.Uint64(), nil - } - return doUint64(rv.Elem().Interface()) - case reflect.Slice: - if rv.Type().Elem().Kind() == reflect.Uint8 { - return gbinary.DecodeToUint64(rv.Bytes()), nil - } - return 0, gerror.NewCodef( - gcode.CodeInvalidParameter, - `unsupport slice type "%s" for converting to uint64`, - rv.Type().String(), - ) - case reflect.String: - var s = rv.String() - // Hexadecimal - if len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') { - v, err := strconv.ParseUint(s[2:], 16, 64) - if err == nil { - return v, nil - } - return 0, gerror.WrapCodef( - gcode.CodeInvalidParameter, - err, - `cannot convert hexadecimal string "%s" to uint64`, - s, - ) - } - // Decimal - if v, err := strconv.ParseUint(s, 10, 64); err == nil { - return v, nil - } - // Float64 - if v, err := doFloat64(any); err == nil { - if math.IsNaN(v) { - return 0, nil - } - return uint64(v), nil - } - default: - if f, ok := any.(localinterface.IUint64); ok { - return f.Uint64(), nil - } - } - return 0, gerror.NewCodef( - gcode.CodeInvalidParameter, - `unsupport value type "%s" for converting to uint64`, - reflect.TypeOf(any).String(), - ) -} diff --git a/util/gconv/gconv_z_bench_struct_test.go b/util/gconv/gconv_z_bench_struct_test.go index 5fb2efe59..a447181b3 100644 --- a/util/gconv/gconv_z_bench_struct_test.go +++ b/util/gconv/gconv_z_bench_struct_test.go @@ -92,7 +92,7 @@ func Benchmark_Struct_Basic(b *testing.B) { func Benchmark_doStruct_Fields8_Basic_MapToStruct(b *testing.B) { for i := 0; i < b.N; i++ { - doStruct(structMapFields8, structPointer8, map[string]string{}, "") + defaultConverter.Struct(structMapFields8, structPointer8, map[string]string{}, "") } } diff --git a/util/gconv/internal/structcache/structcache.go b/util/gconv/internal/structcache/structcache.go index e99fd84c6..ace2236da 100644 --- a/util/gconv/internal/structcache/structcache.go +++ b/util/gconv/internal/structcache/structcache.go @@ -10,9 +10,7 @@ package structcache import ( "reflect" "sync" - "time" - "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/util/gconv/internal/localinterface" ) @@ -23,45 +21,28 @@ 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. - // TODO remove? - customConvertTypeMap map[reflect.Type]struct{} + // typeConverterFuncMap is used to store whether field types are registered to custom conversions + typeConverterFuncMap 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{}), + typeConverterFuncMap: make(map[reflect.Type]struct{}), anyToTypeConvertMap: make(map[reflect.Type]AnyConvertFunc), } } -// RegisterCustomConvertType registers custom -func (cf *ConvertConfig) RegisterCustomConvertType(fieldType reflect.Type) { +// RegisterTypeConvertFunc registers converting function for custom type. +func (cf *ConvertConfig) RegisterTypeConvertFunc(fieldType reflect.Type) { if fieldType.Kind() == reflect.Ptr { fieldType = fieldType.Elem() } - cf.customConvertTypeMap[fieldType] = struct{}{} + cf.typeConverterFuncMap[fieldType] = struct{}{} } // RegisterAnyConvertFunc registers custom type converting function for specified type. diff --git a/util/gconv/internal/structcache/structcache_cached.go b/util/gconv/internal/structcache/structcache_cached.go index ef092f292..98611ae16 100644 --- a/util/gconv/internal/structcache/structcache_cached.go +++ b/util/gconv/internal/structcache/structcache_cached.go @@ -29,7 +29,7 @@ func (cf *ConvertConfig) GetCachedStructInfo(structType reflect.Type, priorityTa // else create one. // it parses and generates a cache info for given struct type. - cachedStructInfo = NewCachedStructInfo(cf.customConvertTypeMap, cf.anyToTypeConvertMap) + cachedStructInfo = NewCachedStructInfo(cf.typeConverterFuncMap, cf.anyToTypeConvertMap) var ( priorityTagArray []string parentIndex = make([]int, 0) diff --git a/util/gconv/internal/structcache/structcache_cached_struct_info.go b/util/gconv/internal/structcache/structcache_cached_struct_info.go index e1e7e4084..b78a3806c 100644 --- a/util/gconv/internal/structcache/structcache_cached_struct_info.go +++ b/util/gconv/internal/structcache/structcache_cached_struct_info.go @@ -18,11 +18,9 @@ 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{} + // typeConverterFuncMap is used to store whether field types are registered to custom conversions, + // for enhance converting performance in runtime. + typeConverterFuncMap map[reflect.Type]struct{} // anyToTypeConvertMap for custom type converting from any to its reflect.Value. anyToTypeConvertMap map[reflect.Type]AnyConvertFunc @@ -39,13 +37,13 @@ type CachedStructInfo struct { // NewCachedStructInfo creates and returns a new CachedStructInfo object. func NewCachedStructInfo( - customConvertTypeMap map[reflect.Type]struct{}, + typeConverterFuncMap map[reflect.Type]struct{}, anyToTypeConvertMap map[reflect.Type]AnyConvertFunc, ) *CachedStructInfo { return &CachedStructInfo{ tagOrFiledNameToFieldInfoMap: make(map[string]*CachedFieldInfo), fieldConvertInfos: make([]*CachedFieldInfo, 0), - customConvertTypeMap: customConvertTypeMap, + typeConverterFuncMap: typeConverterFuncMap, anyToTypeConvertMap: anyToTypeConvertMap, } } @@ -188,6 +186,6 @@ func (csi *CachedStructInfo) checkTypeHasCustomConvert(fieldType reflect.Type) b if fieldType.Kind() == reflect.Ptr { fieldType = fieldType.Elem() } - _, ok := csi.customConvertTypeMap[fieldType] + _, ok := csi.typeConverterFuncMap[fieldType] return ok }