本文转载自:结构体转map[string]interface{}的若干方法 | 李文周的博客
本文介绍了Go语言中将结构体转成map[string]interface{}
时你需要了解的“坑”,也有你需要知道的若干方法。
我们在Go语言中通常使用结构体来保存我们的数据,例如要存储用户信息,我们可能会定义如下结构体:
1 2 3 4 5 6 7 type UserInfo struct { Name string `json:"name"` Age int `json:"age"` } u1 := UserInfo{Name: "q1mi" , Age: 18 }
假设现在要将上面的u1
转换成map[string]interface{}
,该如何操作呢?
结构体转map[string]interface{}
JSON序列化方式
这不是很简单吗?我用JSON序列化一下u1,再反序列化成map不就可以了么。说干就干,代码如下:
1 2 3 4 5 6 7 8 9 10 func main () { u1 := UserInfo{Name: "q1mi" , Age: 18 } b, _ := json.Marshal(&u1) var m map [string ]interface {} _ = json.Unmarshal(b, &m) for k, v := range m{ fmt.Printf("key:%v value:%v\n" , k, v) } }
输出:
1 2 key:name value:q1mi key:age value:18
看起来没什么问题,但其实这里是有一个“坑”的。那就是Go语言中的json
包在序列化空接口存放的数字类型(整型、浮点型等)都会序列化成float64类型。
也就是上面例子中m["age"]
现在底层是一个float64
了,不是个int
了。我们来验证下:
1 2 3 4 5 6 7 8 9 10 func main () { u1 := UserInfo{Name: "q1mi" , Age: 18 } b, _ := json.Marshal(&u1) var m map [string ]interface {} _ = json.Unmarshal(b, &m) for k, v := range m{ fmt.Printf("key:%v value:%v value type:%T\n" , k, v, v) } }
输出:
1 2 key:name value:q1mi value type :string key:age value:18 value type :float64
很显然,出现了一个意料之外的行为,我们需要想办法规避掉。
反射
没办法就需要自己动手去实现了。这里使用反射遍历结构体字段的方式生成map,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func ToMap (in interface {}, tagName string ) (map [string ]interface {}, error ){ out := make (map [string ]interface {}) v := reflect.ValueOf(in) if v.Kind() == reflect.Ptr { v = v.Elem() } if v.Kind() != reflect.Struct { return nil , fmt.Errorf("ToMap only accepts struct or struct pointer; got %T" , v) } t := v.Type() for i := 0 ; i < v.NumField(); i++ { fi := t.Field(i) if tagValue := fi.Tag.Get(tagName); tagValue != "" { out[tagValue] = v.Field(i).Interface() } } return out, nil }
验证一下:
1 2 3 4 m2, _ := ToMap(&u1, "json" )for k, v := range m2{ fmt.Printf("key:%v value:%v value type:%T\n" , k, v, v) }
输出:
1 2 key:name value:q1mi value type :string key:age value:18 value type :int
这一次map["age"]
的类型就对了的。
第三方库structs
除了自己实现,Github上也有现成的轮子,例如第三方库:https://github.com/fatih/structs 。
它使用的自定义结构体tag是structs
:
1 2 3 4 5 type UserInfo struct { Name string `json:"name" structs:"name"` Age int `json:"age" structs:"age"` }
用法很简单:
1 2 3 4 m3 := structs.Map(&u1)for k, v := range m3 { fmt.Printf("key:%v value:%v value type:%T\n" , k, v, v) }
structs
这个包也有很多其他的使用示例,大家可以去查看文档。但是需要注意的是目前这个库已经被作者设置为只读了。
嵌套结构体转map[string]interface{}
structs
本身是支持嵌套结构体转map[string]interface{}
的,遇到结构体嵌套它会转换为map[string]interface{}
嵌套map[string]interface{}
的模式。
我们定义一组嵌套的结构体如下:
1 2 3 4 5 6 7 8 9 10 11 type UserInfo struct { Name string `json:"name" structs:"name"` Age int `json:"age" structs:"age"` Profile `json:"profile" structs:"profile"` }type Profile struct { Hobby string `json:"hobby" structs:"hobby"` }
声明结构体变量u1:
1 u1 := UserInfo{Name: "q1mi" , Age: 18 , Profile: Profile{"双色球" }}
第三方库structs
代码和上面的其实是一样的:
1 2 3 4 m3 := structs.Map(&u1)for k, v := range m3 { fmt.Printf("key:%v value:%v value type:%T\n" , k, v, v) }
输出结果:
1 2 3 key:name value:q1mi value type :string key:age value:18 value type :int key:profile value:map[hobby:双色球] value type :map[string]interface {}
从结果来看最后嵌套字段profile
是map[string]interface {}
,属于map嵌套map。
使用反射转成单层map
如果我们想把嵌套的结构体转换成一个单层map该怎么做呢?
我们把上面反射的代码稍微修改一下就可以了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 func ToMap2 (in interface {}, tag string ) (map [string ]interface {}, error ) { v := reflect.ValueOf(in) if v.Kind() == reflect.Ptr { v = v.Elem() } if v.Kind() != reflect.Struct { return nil , fmt.Errorf("ToMap only accepts struct or struct pointer; got %T" , v) } out := make (map [string ]interface {}) queue := make ([]interface {}, 0 , 1 ) queue = append (queue, in) for len (queue) > 0 { v := reflect.ValueOf(queue[0 ]) if v.Kind() == reflect.Ptr { v = v.Elem() } queue = queue[1 :] t := v.Type() for i := 0 ; i < v.NumField(); i++ { vi := v.Field(i) if vi.Kind() == reflect.Ptr { vi = vi.Elem() if vi.Kind() == reflect.Struct { queue = append (queue, vi.Interface()) } else { ti := t.Field(i) if tagValue := ti.Tag.Get(tag); tagValue != "" { out[tagValue] = vi.Interface() } } break } if vi.Kind() == reflect.Struct { queue = append (queue, vi.Interface()) break } ti := t.Field(i) if tagValue := ti.Tag.Get(tag); tagValue != "" { out[tagValue] = vi.Interface() } } } return out, nil }
测试一下:
1 2 3 4 m4, _ := ToMap2(&u1, "json" )for k, v := range m4 { fmt.Printf("key:%v value:%v value type:%T\n" , k, v, v) }
输出:
1 2 3 key:name value:q1mi value type :string key:age value:18 value type :int key:hobby value:双色球 value type :string
这下我们就把嵌套的结构体转为单层的map了,但是要注意这种场景下结构体和嵌套结构体的字段就需要避免重复。