结构体转map[string]interface{}
-
在go语言中结构体转map[stirng]interface{}中有几种方法,但是也有一些小小的"坑",比如说我们在存储一些信息时有各种类型的,下面来看一个结构体:
// UserInfo 用户信息 type UserInfo struct { Name string `json:"name"` Age int `json:"age"` } u1 := UserInfo{Name: "奇奇", Age: 18}
将此结构体转成map[string]interface{}
-
JSON反序列化
func main() { u1 := UserInfo{Name: "奇奇", 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 ", k, v) } }
看到的结果是:
key:name value:奇奇 key:age value:18
看起来好像没有什么问题,但是,当我们看他们的类型的时候你就会发现有不一样的地方了
我们来验证下func main() { u1 := UserInfo{Name: "奇奇", 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 ", k, v, v) } }
再来看下结果
key:name value:奇奇 value type:string key:age value:18 value type:float64
可以看出age的类型明明是int型的,转成map之后就变成了float64类型了呢
原因是:在JSON协议中没有整型和浮点型之分,它们统称为Number,json字符串中的数组经过go语言中的json经过反序列化之后都会成为float64,但是我们也有办法在转成int型,就是先要将得到的float64经过序列化之后得到json.number类型之后,在转为int类型.
-
反射
除了上面说到的方法之外,我们还有利用反射遍历结构体的方式生成map// ToMap 结构体转为Map[string]interface{} 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() // 遍历结构体字段 // 指定tagName值为map中key;字段值为map中value 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 }
对此可以验证下
m2, _ := ToMap(&u1, "json") for k, v := range m2{ fmt.Printf("key:%v value:%v value type:%T ", k, v, v) }
可以看到输出的结果为:
key:name value:奇奇 value type:string key:age value:18 value type:int
这个看到的就没有问题了,但是,用反射的方式有一点不好就是比较耗性能,因为利用反射的时候不知道你的类型,所以需要一个个去匹配,多以相对来说,性能没有那么好.
-
第三方库structs
除了上述自己实现的以外,在github上面也有一些实现好了的轮子,比如:https://github.com/fatih/structs
它使用的是自定义结构体:struts// UserInfo 用户信息 type UserInfo struct { Name string `json:"name" structs:"name"` Age int `json:"age" structs:"age"` }
用法是:
m3 := structs.Map(&u1) for k, v := range m3 { fmt.Printf("key:%v value:%v value type:%T ", k, v, v) }
是的,是不是方便很多,在这个包中还有其他方法,可以查看文档,据说被作者设置成只读模式
嵌套结构体转map[string]interface{}
-
structs本身是支持嵌套结构体转map[string]interface{}的,遇到结构体嵌套它会转换为map[string]interface{}嵌套map[string]interface{}的模式。
先来定义一个嵌套的结构体:// UserInfo 用户信息 type UserInfo struct { Name string `json:"name" structs:"name"` Age int `json:"age" structs:"age"` Profile `json:"profile" structs:"profile"` } // Profile 配置信息 type Profile struct { Hobby string `json:"hobby" structs:"hobby"` }
申明结构体变了u1
u1 := UserInfo{Name: "奇奇", Age: 18, Profile: Profile{"双色球"}}
利用第三方库structs
m3 := structs.Map(&u1) for k, v := range m3 { fmt.Printf("key:%v value:%v value type:%T ", k, v, v) }
输出结果:
key:name value:奇奇 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转成单层的map呢// ToMap2 将结构体转为单层map func ToMap2(in interface{}, tag string) (map[string]interface{}, error) { // 当前函数只接收struct类型 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 != "" { // 存入map 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 != "" { // 存入map out[tagValue] = vi.Interface() } } } return out, nil }
测试一下:
m4, _ := ToMap2(&u1, "json") for k, v := range m4 { fmt.Printf("key:%v value:%v value type:%T ", k, v, v) }
输出:
key:name value:奇奇 value type:string key:age value:18 value type:int key:hobby value:双色球 value type:string
这下我们就把嵌套的结构体转为单层的map了,但是要注意这种场景下结构体和嵌套结构体的字段就需要避免重复。