GoFrameMap转换详解

示例:

go 复制代码
package main

import (
    "github.com/gogf/gf/v2/frame/g"
    "github.com/gogf/gf/v2/util/gconv"
)



func main() {
    type User struct {
       Uid  string `c:"uid"`
       Name string `c:"name"`
    }

    //对象.
    g.Dump(gconv.Map(User{Uid: "1", Name: "john"}))
}

输出结果:

gconv.Map()方法:

go 复制代码
func Map(value any, option ...MapOption) map[string]any {
    result, _ := defaultConverter.Map(value, getUsedMapOption(option...))
    return result
}

因为调用没有传入这个值.然后通过断点验证可以知道option为nil.

getUsedMapOption()方法:

go 复制代码
func getUsedMapOption(option ...MapOption) MapOption {
    var usedOption = MapOption{
       ContinueOnError: true,
    }
    if len(option) > 0 {
       usedOption = option[0]
    }
    return usedOption
}

defaultConverter.Map()方法:

go 复制代码
func (c *Converter) Map(value any, option ...MapOption) (map[string]any, error) {
    return c.doMapConvert(value, RecursiveTypeAuto, false, c.getMapOption(option...))
}

RecursiveTypeAuto的值提前定义好的.

c.doMapConvert()方法:

go 复制代码
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
}

gtag.StructTagPriority:

csharp 复制代码
var StructTagPriority = []string{
    GConv, Param, GConvShort, ParamShort, Json,
}

MapOption结构体:

go 复制代码
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
}

上面获取这个结构体的时候.因为没有给Deep赋值.所以这里都默认为false.所以recursive还是auto.

反射处理默认分支.

首先将 value 转为 reflect.Value(若已是 reflect.Value 则直接使用)。

引用指针,直到得到非指针的 reflectValuereflectKind

根据 reflectKind 分情况:reflect.Slicereflect.Array

将数组/切片视为键值交替的序列:

偶数索引为键,奇数索引为值(例如 ["k1","v1","k2","v2"]{"k1":"v1","k2":"v2"})。

若长度为奇数,最后一个键对应的值为 nil

每个键通过 c.String() 转换为字符串,若出错且不忽略错误则返回。

reflect.Mapreflect.Structreflect.Interface

调用 c.doMapConvertForMapOrStructValue 进行转换,该函数会递归处理 map/struct,并根据 recursiveMustMapReturn 决定最终输出。

若返回的值确实是 map[string]any,则返回它;否则返回 nil, nil

其他类型

直接返回 nil, nil,不进行转换。

doMapConvertForMapOrStructValue()方法:

php 复制代码
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
}

下面又会对值进行各种类型的判断转换.可以参考上面的.

语雀地址www.yuque.com/itbosunmian...?

《Go.》 密码:xbkk 欢迎大家访问.提意见.

相关推荐
小江的记录本1 小时前
【MySQL】《MySQL日志面试背诵版+思维导图》(核心考点 + MySQL 8.0最新优化)
java·数据库·后端·python·sql·mysql·面试
yoyo_zzm1 小时前
PHP vs Java:后端语言终极选择指南
java·spring boot·后端·架构·php
苏三说技术2 小时前
从索引失效到性能翻倍,DBA不愿透露的10个优化技巧
后端
神奇小汤圆2 小时前
Java AI 框架选型:LangChain4j 还是 Spring AI?
后端
Moment2 小时前
刷 Reddit 1 小时没结果?我用这个方法 10 秒挖出真实需求
前端·javascript·后端
神奇小汤圆2 小时前
小米二面:Redis为什么能支撑10万+QPS?
后端
学不思则罔2 小时前
SpringBoot启动失败排查指南
spring boot·后端·部署
喵个咪2 小时前
Kratos KCP 传输中间件:游戏开发低延迟网络通信实战指南
后端·微服务·游戏开发
喵个咪3 小时前
Kratos 生态双定时器中间件:高精度 hptimer 与标准 cron 选型与实践
后端·微服务·go