mirror of
https://gitee.com/johng/gf
synced 2026-07-01 03:21:22 +08:00
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.
655 lines
18 KiB
Go
655 lines
18 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/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"
|
|
)
|
|
|
|
// MapOption specifies the option for map converting.
|
|
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]any type variable.
|
|
Deep bool
|
|
|
|
// OmitEmpty ignores the attributes that has json `omitempty` tag.
|
|
OmitEmpty bool
|
|
|
|
// Tags specifies the converted map key name by struct tag name.
|
|
Tags []string
|
|
|
|
// ContinueOnError specifies whether to continue converting the next element
|
|
// if one element converting fails.
|
|
ContinueOnError bool
|
|
}
|
|
|
|
func (c *Converter) getMapOption(option ...MapOption) MapOption {
|
|
if len(option) > 0 {
|
|
return option[0]
|
|
}
|
|
return MapOption{}
|
|
}
|
|
|
|
// 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 (c *Converter) Map(value any, option ...MapOption) (map[string]any, error) {
|
|
return c.doMapConvert(value, RecursiveTypeAuto, false, c.getMapOption(option...))
|
|
}
|
|
|
|
// MapStrStr converts `value` to map[string]string.
|
|
// Note that there might be data copy for this map type converting.
|
|
func (c *Converter) MapStrStr(value any, option ...MapOption) (map[string]string, error) {
|
|
if r, ok := value.(map[string]string); ok {
|
|
return r, nil
|
|
}
|
|
m, err := c.Map(value, option...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(m) > 0 {
|
|
var (
|
|
s string
|
|
vMap = make(map[string]string, len(m))
|
|
mapOption = c.getMapOption(option...)
|
|
)
|
|
for k, v := range m {
|
|
s, err = c.String(v)
|
|
if err != nil && !mapOption.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
vMap[k] = s
|
|
}
|
|
return vMap, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// 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 any, recursive RecursiveType, mustMapReturn bool, option MapOption,
|
|
) (map[string]any, error) {
|
|
if value == nil {
|
|
return nil, nil
|
|
}
|
|
// It redirects to its underlying value if it has implemented interface iVal.
|
|
if v, ok := value.(localinterface.IVal); ok {
|
|
value = v.Val()
|
|
}
|
|
var (
|
|
err error
|
|
newTags = gtag.StructTagPriority
|
|
)
|
|
if option.Deep {
|
|
recursive = RecursiveTypeTrue
|
|
}
|
|
switch len(option.Tags) {
|
|
case 0:
|
|
// No need handling.
|
|
case 1:
|
|
newTags = append(strings.Split(option.Tags[0], ","), gtag.StructTagPriority...)
|
|
default:
|
|
newTags = append(option.Tags, gtag.StructTagPriority...)
|
|
}
|
|
// Assert the common combination of types, and finally it uses reflection.
|
|
dataMap := make(map[string]any)
|
|
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, err
|
|
}
|
|
} else {
|
|
return nil, 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, err
|
|
}
|
|
} else {
|
|
return nil, nil
|
|
}
|
|
case map[any]any:
|
|
recursiveOption := option
|
|
recursiveOption.Tags = newTags
|
|
for k, v := range r {
|
|
s, err := c.String(k)
|
|
if err != nil && !option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
dataMap[s], err = c.doMapConvertForMapOrStructValue(
|
|
doMapConvertForMapOrStructValueInput{
|
|
IsRoot: false,
|
|
Value: v,
|
|
RecursiveType: recursive,
|
|
RecursiveOption: recursive == RecursiveTypeTrue,
|
|
Option: recursiveOption,
|
|
},
|
|
)
|
|
if err != nil && !option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
}
|
|
case map[any]string:
|
|
for k, v := range r {
|
|
s, err := c.String(k)
|
|
if err != nil && !option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
dataMap[s] = v
|
|
}
|
|
case map[any]int:
|
|
for k, v := range r {
|
|
s, err := c.String(k)
|
|
if err != nil && !option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
dataMap[s] = v
|
|
}
|
|
case map[any]uint:
|
|
for k, v := range r {
|
|
s, err := c.String(k)
|
|
if err != nil && !option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
dataMap[s] = v
|
|
}
|
|
case map[any]float32:
|
|
for k, v := range r {
|
|
s, err := c.String(k)
|
|
if err != nil && !option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
dataMap[s] = v
|
|
}
|
|
case map[any]float64:
|
|
for k, v := range r {
|
|
s, err := c.String(k)
|
|
if err != nil && !option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
dataMap[s] = 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]any:
|
|
if recursive == RecursiveTypeTrue {
|
|
recursiveOption := option
|
|
recursiveOption.Tags = newTags
|
|
// A copy of current map.
|
|
for k, v := range r {
|
|
dataMap[k], err = c.doMapConvertForMapOrStructValue(
|
|
doMapConvertForMapOrStructValueInput{
|
|
IsRoot: false,
|
|
Value: v,
|
|
RecursiveType: recursive,
|
|
RecursiveOption: recursive == RecursiveTypeTrue,
|
|
Option: recursiveOption,
|
|
},
|
|
)
|
|
if err != nil && !option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
}
|
|
} else {
|
|
// It returns the map directly without any changing.
|
|
return r, nil
|
|
}
|
|
case map[int]any:
|
|
recursiveOption := option
|
|
recursiveOption.Tags = newTags
|
|
for k, v := range r {
|
|
s, err := c.String(k)
|
|
if err != nil && !option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
dataMap[s], err = c.doMapConvertForMapOrStructValue(
|
|
doMapConvertForMapOrStructValueInput{
|
|
IsRoot: false,
|
|
Value: v,
|
|
RecursiveType: recursive,
|
|
RecursiveOption: recursive == RecursiveTypeTrue,
|
|
Option: recursiveOption,
|
|
},
|
|
)
|
|
if err != nil && !option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
}
|
|
case map[int]string:
|
|
for k, v := range r {
|
|
s, err := c.String(k)
|
|
if err != nil && !option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
dataMap[s] = v
|
|
}
|
|
case map[uint]string:
|
|
for k, v := range r {
|
|
s, err := c.String(k)
|
|
if err != nil && !option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
dataMap[s] = 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.Pointer {
|
|
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]any{"k1":"v1", "k2":"v2"}
|
|
// []string{"k1","v1","k2"} => map[string]any{"k1":"v1", "k2":nil}
|
|
case reflect.Slice, reflect.Array:
|
|
length := reflectValue.Len()
|
|
for i := 0; i < length; i += 2 {
|
|
s, err := c.String(reflectValue.Index(i).Interface())
|
|
if err != nil && !option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
if i+1 < length {
|
|
dataMap[s] = reflectValue.Index(i + 1).Interface()
|
|
} else {
|
|
dataMap[s] = nil
|
|
}
|
|
}
|
|
case reflect.Map, reflect.Struct, reflect.Interface:
|
|
recursiveOption := option
|
|
recursiveOption.Tags = newTags
|
|
convertedValue, err := c.doMapConvertForMapOrStructValue(
|
|
doMapConvertForMapOrStructValueInput{
|
|
IsRoot: true,
|
|
Value: value,
|
|
RecursiveType: recursive,
|
|
RecursiveOption: recursive == RecursiveTypeTrue,
|
|
Option: recursiveOption,
|
|
MustMapReturn: mustMapReturn,
|
|
},
|
|
)
|
|
if err != nil && !option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
if m, ok := convertedValue.(map[string]any); ok {
|
|
return m, nil
|
|
}
|
|
return nil, nil
|
|
default:
|
|
return nil, nil
|
|
}
|
|
}
|
|
return dataMap, nil
|
|
}
|
|
|
|
type doMapConvertForMapOrStructValueInput struct {
|
|
IsRoot bool // It returns directly if it is not root and with no recursive converting.
|
|
Value any // 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) (any, error) {
|
|
if !in.IsRoot && !in.RecursiveOption {
|
|
return in.Value, nil
|
|
}
|
|
|
|
var (
|
|
err error
|
|
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.Pointer {
|
|
reflectValue = reflectValue.Elem()
|
|
reflectKind = reflectValue.Kind()
|
|
}
|
|
switch reflectKind {
|
|
case reflect.Map:
|
|
var (
|
|
mapIter = reflectValue.MapRange()
|
|
dataMap = make(map[string]any)
|
|
)
|
|
for mapIter.Next() {
|
|
var (
|
|
mapKeyValue = mapIter.Value()
|
|
mapValue any
|
|
)
|
|
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()
|
|
}
|
|
s, err := c.String(mapIter.Key().Interface())
|
|
if err != nil && !in.Option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
dataMap[s], err = c.doMapConvertForMapOrStructValue(
|
|
doMapConvertForMapOrStructValueInput{
|
|
IsRoot: false,
|
|
Value: mapValue,
|
|
RecursiveType: in.RecursiveType,
|
|
RecursiveOption: in.RecursiveType == RecursiveTypeTrue,
|
|
Option: in.Option,
|
|
},
|
|
)
|
|
if err != nil && !in.Option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
}
|
|
return dataMap, nil
|
|
|
|
case reflect.Struct:
|
|
var dataMap = make(map[string]any)
|
|
// 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], err = c.doMapConvertForMapOrStructValue(
|
|
doMapConvertForMapOrStructValueInput{
|
|
IsRoot: false,
|
|
Value: mapV,
|
|
RecursiveType: in.RecursiveType,
|
|
RecursiveOption: in.RecursiveType == RecursiveTypeTrue,
|
|
Option: in.Option,
|
|
},
|
|
)
|
|
if err != nil && !in.Option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
dataMap[mapK] = mapV
|
|
}
|
|
}
|
|
if len(dataMap) > 0 {
|
|
return dataMap, nil
|
|
}
|
|
}
|
|
// 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.Pointer {
|
|
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, err := c.doMapConvertForMapOrStructValue(
|
|
doMapConvertForMapOrStructValueInput{
|
|
IsRoot: false,
|
|
Value: rvInterface,
|
|
RecursiveType: in.RecursiveType,
|
|
RecursiveOption: true,
|
|
Option: in.Option,
|
|
},
|
|
)
|
|
if err != nil && !in.Option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
if m, ok := anonymousValue.(map[string]any); 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], err = c.doMapConvertForMapOrStructValue(
|
|
doMapConvertForMapOrStructValueInput{
|
|
IsRoot: false,
|
|
Value: rvInterface,
|
|
RecursiveType: in.RecursiveType,
|
|
RecursiveOption: true,
|
|
Option: in.Option,
|
|
},
|
|
)
|
|
if err != nil && !in.Option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
|
|
default:
|
|
dataMap[mapKey], err = c.doMapConvertForMapOrStructValue(
|
|
doMapConvertForMapOrStructValueInput{
|
|
IsRoot: false,
|
|
Value: rvInterface,
|
|
RecursiveType: in.RecursiveType,
|
|
RecursiveOption: in.RecursiveType == RecursiveTypeTrue,
|
|
Option: in.Option,
|
|
},
|
|
)
|
|
if err != nil && !in.Option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// 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([]any, length)
|
|
for arrayIndex := 0; arrayIndex < length; arrayIndex++ {
|
|
array[arrayIndex], err = c.doMapConvertForMapOrStructValue(
|
|
doMapConvertForMapOrStructValueInput{
|
|
IsRoot: false,
|
|
Value: rvAttrField.Index(arrayIndex).Interface(),
|
|
RecursiveType: in.RecursiveType,
|
|
RecursiveOption: in.RecursiveType == RecursiveTypeTrue,
|
|
Option: in.Option,
|
|
},
|
|
)
|
|
if err != nil && !in.Option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
}
|
|
dataMap[mapKey] = array
|
|
case reflect.Map:
|
|
var (
|
|
mapIter = rvAttrField.MapRange()
|
|
nestedMap = make(map[string]any)
|
|
)
|
|
for mapIter.Next() {
|
|
s, err := c.String(mapIter.Key().Interface())
|
|
if err != nil && !in.Option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
nestedMap[s], err = c.doMapConvertForMapOrStructValue(
|
|
doMapConvertForMapOrStructValueInput{
|
|
IsRoot: false,
|
|
Value: mapIter.Value().Interface(),
|
|
RecursiveType: in.RecursiveType,
|
|
RecursiveOption: in.RecursiveType == RecursiveTypeTrue,
|
|
Option: in.Option,
|
|
},
|
|
)
|
|
if err != nil && !in.Option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
}
|
|
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, nil
|
|
}
|
|
return dataMap, nil
|
|
|
|
// The given value is type of slice.
|
|
case reflect.Array, reflect.Slice:
|
|
length := reflectValue.Len()
|
|
if length == 0 {
|
|
break
|
|
}
|
|
array := make([]any, reflectValue.Len())
|
|
for i := 0; i < length; i++ {
|
|
array[i], err = c.doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{
|
|
IsRoot: false,
|
|
Value: reflectValue.Index(i).Interface(),
|
|
RecursiveType: in.RecursiveType,
|
|
RecursiveOption: in.RecursiveType == RecursiveTypeTrue,
|
|
Option: in.Option,
|
|
})
|
|
if err != nil && !in.Option.ContinueOnError {
|
|
return nil, err
|
|
}
|
|
}
|
|
return array, nil
|
|
|
|
default:
|
|
}
|
|
return in.Value, nil
|
|
}
|