Files
gf/util/gconv/internal/converter/converter_struct.go
hailaz ee24da4e72 refactor: interface{} to any and reflect.Ptr to reflect.Pointer (#4395)
This pull request standardizes the use of the Go 1.18+ `any` type alias
instead of `interface{}` throughout the codebase. The change improves
code readability and aligns with modern Go best practices. The update
touches many files, including core data structures, code generation
templates, logging utilities, and test data, ensuring consistency across
all usages.

**Type alias migration to `any`:**

* Replaced all instances of `interface{}` with `any` in core data
structures such as `garray` and in generated model structs (e.g.,
`TableUser`, `User1`, `User2`) to modernize type usage.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[3]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[4]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[5]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[6]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
* Updated function signatures, method parameters, and return types from
`interface{}` to `any` in various parts of the codebase, including code
generation, service logic, and logging utilities (e.g., `mlog`).
[[1]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[2]](diffhunk://#diff-2b1953fb78cf3593d8c2c7d911e95b65fd0b847c30ed0b4d167d16fe6d781235L54-R74)
[[3]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[4]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)
[[5]](diffhunk://#diff-c5d51d56f487779a2b6207c7ad26c7a20bbadcc846ce094fe60ab4cabff58c51L107-R107)
[[6]](diffhunk://#diff-f96e6a9fdb416eb1804ceaba1fe0ac637bff22c43837f8bb849c2366ce72d4a1L116-R121)
[[7]](diffhunk://#diff-f94c83a1b08ae060d9346f4a6031fc4a7b9a0b894e02d9afaa09018b6598eac0L112-R112)
[[8]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L36-R36)
[[9]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L74-R74)
[[10]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L96-R96)

**Generated code and templates:**

* Adjusted generated files and code generation templates to output `any`
instead of `interface{}` for relevant struct fields and function
signatures, ensuring that new code generation aligns with the updated
convention.
[[1]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[2]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[3]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[4]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[5]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
[[6]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[7]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[8]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)

**Container and utility updates:**

* Refactored the `garray` container implementation and related
constructors/methods to use `[]any` instead of `[]interface{}`, along
with corresponding function signatures.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L52-R52)
[[3]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L62-R62)
[[4]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L73-R86)
[[5]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L96-R97)
[[6]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L107-R114)
[[7]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L124-R124)
[[8]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L135-R143)
[[9]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L167-R167)

These changes collectively modernize the codebase and prepare it for
future Go developments by using the idiomatic `any` type.
2025-08-28 16:53:19 +08:00

675 lines
20 KiB
Go

// 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 converter
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"
)
// StructOption is the option for Struct converting.
type StructOption struct {
// ParamKeyToAttrMap is the map for custom parameter key to attribute name mapping.
ParamKeyToAttrMap map[string]string
// PriorityTag is the priority tag for struct converting.
PriorityTag string
// ContinueOnError specifies whether to continue converting the next element
// if one element converting fails.
ContinueOnError bool
}
func (c *Converter) getStructOption(option ...StructOption) StructOption {
if len(option) > 0 {
return option[0]
}
return StructOption{}
}
// Struct is the core internal converting function for any data to struct.
func (c *Converter) Struct(params, pointer any, option ...StructOption) (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 := c.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 (
structOption = c.getStructOption(option...)
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.Pointer {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
"destination pointer should be type of '*struct', but got '%v'",
pointerReflectKind,
)
}
// Using IsNil on reflect.Pointer 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 = c.doConvertWithTypeCheck(paramsReflectValue, pointerElemReflectValue); ok {
return nil
}
// custom convert.
ok, err = c.callCustomConverter(paramsReflectValue, pointerReflectValue)
if err != nil && !structOption.ContinueOnError {
return err
}
if ok {
return nil
}
// 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.Pointer {
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, err = c.doMapConvert(paramsInterface, RecursiveTypeAuto, true, MapOption{
ContinueOnError: structOption.ContinueOnError,
})
if err != nil {
return err
}
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.internalConverter.GetCachedStructInfo(
pointerElemReflectValue.Type(), structOption.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 structOption.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,
structOption,
); err != nil {
return err
}
if len(cachedFieldInfo.OtherSameNameField) > 0 {
if err = c.setOtherSameNameField(
cachedFieldInfo, paramsValue, pointerReflectValue, structOption,
); 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,
usedParamsKeyOrTagNameMap, cachedStructInfo,
structOption,
)
}
func (c *Converter) setOtherSameNameField(
cachedFieldInfo *structcache.CachedFieldInfo,
srcValue any,
structValue reflect.Value,
option StructOption,
) (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, option); err != nil {
return err
}
}
return nil
}
func (c *Converter) bindStructWithLoopFieldInfos(
paramsMap map[string]any,
structValue reflect.Value,
usedParamsKeyOrTagNameMap map[string]struct{},
cachedStructInfo *structcache.CachedStructInfo,
option StructOption,
) (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, option,
); err != nil && !option.ContinueOnError {
return err
}
// handle same field name in nested struct.
if len(cachedFieldInfo.OtherSameNameField) > 0 {
if err = c.setOtherSameNameField(
cachedFieldInfo, paramValue, structValue, option,
); err != nil && !option.ContinueOnError {
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, option,
); err != nil && !option.ContinueOnError {
return err
}
// handle same field name in nested struct.
if len(cachedFieldInfo.OtherSameNameField) > 0 {
if err = c.setOtherSameNameField(
cachedFieldInfo, paramValue, structValue, option,
); err != nil && !option.ContinueOnError {
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,
option StructOption,
) (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, option); 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.HasCustomConvert {
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)
}
convertOption := ConvertOption{
StructOption: option,
SliceOption: SliceOption{ContinueOnError: option.ContinueOnError},
MapOption: MapOption{ContinueOnError: option.ContinueOnError},
}
err = c.doConvertWithReflectValueSet(
fieldValue, doConvertInput{
FromValue: srcValue,
ToTypeName: cachedFieldInfo.StructField.Type.String(),
ReferValue: fieldValue,
},
convertOption,
)
return err
}
// bindVarToReflectValueWithInterfaceCheck does bind using common interfaces checks.
func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value any) (bool, error) {
var pointer any
if reflectValue.Kind() != reflect.Pointer && 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, option StructOption) (err error) {
// JSON content converting.
ok, err := c.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.Pointer, 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, option.ParamKeyToAttrMap, MapOption{
ContinueOnError: option.ContinueOnError,
})
case reflect.Struct:
// Recursively converting for struct attribute.
if err = c.Struct(value, structFieldValue, option); 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)
convertOption = ConvertOption{
StructOption: option,
SliceOption: SliceOption{ContinueOnError: option.ContinueOnError},
MapOption: MapOption{ContinueOnError: option.ContinueOnError},
}
)
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.Pointer {
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, option); err == nil {
converted = true
}
}
if !converted {
err = c.doConvertWithReflectValueSet(
elem, doConvertInput{
FromValue: reflectValue.Index(i).Interface(),
ToTypeName: elemTypeName,
ReferValue: elem,
},
convertOption,
)
if err != nil {
return err
}
}
if elemType.Kind() == reflect.Pointer {
// 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.Pointer {
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.Pointer {
elem = reflect.New(elemType.Elem()).Elem()
} else {
elem = reflect.New(elemType).Elem()
}
if elem.Kind() == reflect.Struct {
if err = c.Struct(value, elem, option); err == nil {
converted = true
}
}
if !converted {
err = c.doConvertWithReflectValueSet(
elem, doConvertInput{
FromValue: value,
ToTypeName: elemTypeName,
ReferValue: elem,
},
convertOption,
)
if err != nil {
return err
}
}
if elemType.Kind() == reflect.Pointer {
// 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.Pointer:
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, option); err == nil {
structFieldValue.Set(elem.Addr())
}
} else {
// Not empty pointer, it assigns values to it.
return c.bindVarToReflectValue(structFieldValue.Elem(), value, option)
}
// 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
}