本文介绍了Go语言中将结构体转成map[string]interface{}时你需要了解的“坑”,也有你需要知道的若干方法。
我们在Go语言中通常使用结构体来保存我们的数据,例如要存储用户信息,我们可能会定义如下结构体:

  1. // UserInfo 用户信息
  2. type UserInfo struct {
  3. Name string `json:"name"`
  4. Age int `json:"age"`
  5. }
  6. u1 := UserInfo{Name: "q1mi", Age: 18}

假设现在要将上面的u1转换成map[string]interface{},该如何操作呢?

结构体转map[string]interface{}

JSON序列化方式

这不是很简单吗?我用JSON序列化一下u1,再反序列化成map不就可以了么。说干就干,代码如下:

  1. func main() {
  2. u1 := UserInfo{Name: "q1mi", Age: 18}
  3. b, _ := json.Marshal(&u1)
  4. var m map[string]interface{}
  5. _ = json.Unmarshal(b, &m)
  6. for k, v := range m{
  7. fmt.Printf("key:%v value:%v\n", k, v)
  8. }
  9. }

输出:

  1. key:name value:q1mi
  2. key:age value:18

看起来没什么问题,但其实这里是有一个“坑”的。那就是Go语言中的json包在序列化空接口存放的数字类型(整型、浮点型等)都会序列化成float64类型。
也就是上面例子中m["age"]现在底层是一个float64了,不是个int了。我们来验证下:

  1. func main() {
  2. u1 := UserInfo{Name: "q1mi", Age: 18}
  3. b, _ := json.Marshal(&u1)
  4. var m map[string]interface{}
  5. _ = json.Unmarshal(b, &m)
  6. for k, v := range m{
  7. fmt.Printf("key:%v value:%v value type:%T\n", k, v, v)
  8. }
  9. }

输出:

  1. key:name value:q1mi value type:string
  2. key:age value:18 value type:float64

很显然,出现了一个意料之外的行为,我们需要想办法规避掉。

反射

没办法就需要自己动手去实现了。这里使用反射遍历结构体字段的方式生成map,具体代码如下:

  1. // ToMap 结构体转为Map[string]interface{}
  2. func ToMap(in interface{}, tagName string) (map[string]interface{}, error){
  3. out := make(map[string]interface{})
  4. v := reflect.ValueOf(in)
  5. if v.Kind() == reflect.Ptr {
  6. v = v.Elem()
  7. }
  8. if v.Kind() != reflect.Struct { // 非结构体返回错误提示
  9. return nil, fmt.Errorf("ToMap only accepts struct or struct pointer; got %T", v)
  10. }
  11. t := v.Type()
  12. // 遍历结构体字段
  13. // 指定tagName值为map中key;字段值为map中value
  14. for i := 0; i < v.NumField(); i++ {
  15. fi := t.Field(i)
  16. if tagValue := fi.Tag.Get(tagName); tagValue != "" {
  17. out[tagValue] = v.Field(i).Interface()
  18. }
  19. }
  20. return out, nil
  21. }

验证一下:

  1. m2, _ := ToMap(&u1, "json")
  2. for k, v := range m2{
  3. fmt.Printf("key:%v value:%v value type:%T\n", k, v, v)
  4. }

输出:

  1. key:name value:q1mi value type:string
  2. key:age value:18 value type:int

这一次map["age"]的类型就对了的。

第三方库structs

除了自己实现,Github上也有现成的轮子,例如第三方库:https://github.com/fatih/structs
它使用的自定义结构体tag是structs:

  1. // UserInfo 用户信息
  2. type UserInfo struct {
  3. Name string `json:"name" structs:"name"`
  4. Age int `json:"age" structs:"age"`
  5. }

用法很简单:

  1. m3 := structs.Map(&u1)
  2. for k, v := range m3 {
  3. fmt.Printf("key:%v value:%v value type:%T\n", k, v, v)
  4. }

structs这个包也有很多其他的使用示例,大家可以去查看文档。但是需要注意的是目前这个库已经被作者设置为只读了。

嵌套结构体转map[string]interface{}

structs本身是支持嵌套结构体转map[string]interface{}的,遇到结构体嵌套它会转换为map[string]interface{}嵌套map[string]interface{}的模式。
我们定义一组嵌套的结构体如下:

  1. // UserInfo 用户信息
  2. type UserInfo struct {
  3. Name string `json:"name" structs:"name"`
  4. Age int `json:"age" structs:"age"`
  5. Profile `json:"profile" structs:"profile"`
  6. }
  7. // Profile 配置信息
  8. type Profile struct {
  9. Hobby string `json:"hobby" structs:"hobby"`
  10. }

声明结构体变量u1:

  1. u1 := UserInfo{Name: "q1mi", Age: 18, Profile: Profile{"双色球"}}

第三方库structs

代码和上面的其实是一样的:

  1. m3 := structs.Map(&u1)
  2. for k, v := range m3 {
  3. fmt.Printf("key:%v value:%v value type:%T\n", k, v, v)
  4. }

输出结果:

  1. key:name value:q1mi value type:string
  2. key:age value:18 value type:int
  3. key:profile value:map[hobby:双色球] value type:map[string]interface {}

从结果来看最后嵌套字段profilemap[string]interface {},属于map嵌套map。

使用反射转成单层map

如果我们想把嵌套的结构体转换成一个单层map该怎么做呢?
我们把上面反射的代码稍微修改一下就可以了:

  1. // ToMap2 将结构体转为单层map
  2. func ToMap2(in interface{}, tag string) (map[string]interface{}, error) {
  3. // 当前函数只接收struct类型
  4. v := reflect.ValueOf(in)
  5. if v.Kind() == reflect.Ptr { // 结构体指针
  6. v = v.Elem()
  7. }
  8. if v.Kind() != reflect.Struct {
  9. return nil, fmt.Errorf("ToMap only accepts struct or struct pointer; got %T", v)
  10. }
  11. out := make(map[string]interface{})
  12. queue := make([]interface{}, 0, 1)
  13. queue = append(queue, in)
  14. for len(queue) > 0 {
  15. v := reflect.ValueOf(queue[0])
  16. if v.Kind() == reflect.Ptr { // 结构体指针
  17. v = v.Elem()
  18. }
  19. queue = queue[1:]
  20. t := v.Type()
  21. for i := 0; i < v.NumField(); i++ {
  22. vi := v.Field(i)
  23. if vi.Kind() == reflect.Ptr { // 内嵌指针
  24. vi = vi.Elem()
  25. if vi.Kind() == reflect.Struct { // 结构体
  26. queue = append(queue, vi.Interface())
  27. } else {
  28. ti := t.Field(i)
  29. if tagValue := ti.Tag.Get(tag); tagValue != "" {
  30. // 存入map
  31. out[tagValue] = vi.Interface()
  32. }
  33. }
  34. break
  35. }
  36. if vi.Kind() == reflect.Struct { // 内嵌结构体
  37. queue = append(queue, vi.Interface())
  38. break
  39. }
  40. // 一般字段
  41. ti := t.Field(i)
  42. if tagValue := ti.Tag.Get(tag); tagValue != "" {
  43. // 存入map
  44. out[tagValue] = vi.Interface()
  45. }
  46. }
  47. }
  48. return out, nil
  49. }

测试一下:

  1. m4, _ := ToMap2(&u1, "json")
  2. for k, v := range m4 {
  3. fmt.Printf("key:%v value:%v value type:%T\n", k, v, v)
  4. }

输出:

  1. key:name value:q1mi value type:string
  2. key:age value:18 value type:int
  3. key:hobby value:双色球 value type:string

这下我们就把嵌套的结构体转为单层的map了,但是要注意这种场景下结构体和嵌套结构体的字段就需要避免重复。