在Golang中,如何将一个结构体转成map? 本文介绍两种方法。第一种是是使用json
包解析解码编码。第二种是使用反射,使用反射的效率比较高,代码在这里 。
假设有下面的一个结构体
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 func newUser () User { name := "user" MyGithub := GithubPage{ URL: "https://github.com/liangyaopei" , Star: 1 , } NoDive := StructNoDive{NoDive: 1 } dateStr := "2020-07-21 12:00:00" date, _ := time.Parse(timeLayout, dateStr) profile := Profile{ Experience: "my experience" , Date: date, } return User{ Name: name, Github: MyGithub, NoDive: NoDive, MyProfile: profile, } } type User struct { Name string `map:"name,omitempty"` Github GithubPage `map:"github,dive,omitempty"` NoDive StructNoDive `map:"no_dive,omitempty"` MyProfile Profile `map:"my_profile,omitempty"` } type GithubPage struct { URL string `map:"url"` Star int `map:"star"` } type StructNoDive struct { NoDive int } type Profile struct { Experience string `map:"experience"` Date time.Time `map:"time"` } func (p Profile) StructToMap () (key string , value interface {}) { return "time" , p.Date.Format(timeLayout) }
json包的marshal,unmarshal 先将结构体序列化成[]byte
数组,再从[]byte
数组序列化成结构体。
1 2 3 data, _ := json.Marshal(&user) m := make (map [string ]interface {}) json.Unmarshal(data, &m)
优势
使用简单 劣势
效率比较慢
不能支持一些定制的键,也不能支持一些定制的方法,例如将struct的域展开等。
使用反射 本文实现了使用反射将结构体转成map
的方法。通过标签(tag)和反射,将上文示例的newUser()
返回的结果转化成下面的一个map
。其中包含struct的域的展开,定制化struct的方法。
1 2 3 4 5 6 7 8 9 map [string ]interface {}{ "name" : "user" , "no_dive" : StructNoDive{NoDive: 1 }, "url" : "https://github.com/liangyaopei" , "star" : 1 , "time" : "2020-07-21 12:00:00" , }
实现思路 & 源码解析 1.标签识别。 使用readTag
方法读取域(field)的标签,如果没有标签,使用域的名字。然后读取tag中的选项。目前支持3个选项
‘-‘:忽略当前这个域
‘omitempty’ : 当这个域的值为空,忽略这个域
‘dive’ : 递归地遍历这个结构体,将所有字段作为键
如果选中了一个选项,就讲这个域对应的二进制位置为1.。
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 const ( OptIgnore = "-" OptOmitempty = "omitempty" OptDive = "dive" ) const ( flagIgnore = 1 << iota flagOmiEmpty flagDive ) func readTag (f reflect.StructField, tag string ) (string , int ) { val, ok := f.Tag.Lookup(tag) fieldTag := "" flag := 0 if !ok { return f.Name, flag } opts := strings.Split(val, "," ) fieldTag = opts[0 ] for i := 1 ; i < len (opts); i++ { switch opts[i] { case OptIgnore: flag |= flagIgnore case OptOmitempty: flag |= flagOmiEmpty case OptDive: flag |= flagDive } } return fieldTag, flag }
2.结构体的域(field)的遍历。 遍历结构体的每一个域(field),判断field的类型(kind)。如果是string
,int
等的基本类型,直接取值,并且把标签中的值作为key。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 for i := 0 ; i < t.NumField(); i++ { ... switch fieldValue.Kind() { case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64: res[tagVal] = fieldValue.Int() case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64: res[tagVal] = fieldValue.Uint() case reflect.Float32, reflect.Float64: res[tagVal] = fieldValue.Float() case reflect.String: res[tagVal] = fieldValue.String() case reflect.Bool: res[tagVal] = fieldValue.Bool() default : } } }
3.内嵌结构体的转换 如果是结构体,先检查有没有实现传入参数的方法,如果实现了,就调用这个方法。如果没有实现,就递归地调用StructToMap
方法,然后根据是否展开(dive
),来把返回结果写入res的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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 for i := 0 ; i < t.NumField(); i++ { fieldType := t.Field(i) if fieldType.PkgPath != "" { continue } tagVal, flag := readTag(fieldType, tag) if flag&flagIgnore != 0 { continue } fieldValue := v.Field(i) if flag&flagOmiEmpty != 0 && fieldValue.IsZero() { continue } if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() { continue } if fieldValue.Kind() == reflect.Ptr { fieldValue = fieldValue.Elem() } switch fieldValue.Kind() { case reflect.Struct: _, ok := fieldValue.Type().MethodByName(methodName) if ok { key, value, err := callFunc(fieldValue, methodName) if err != nil { return nil , err } res[key] = value continue } deepRes, deepErr := StructToMap(fieldValue.Interface(), tag, methodName) if deepErr != nil { return nil , deepErr } if flag&flagDive != 0 { for k, v := range deepRes { res[k] = v } } else { res[tagVal] = deepRes } default : } } ... } func callFunc (fv reflect.Value, methodName string ) (string , interface {}, error) { methodRes := fv.MethodByName(methodName).Call([]reflect.Value{}) if len (methodRes) != methodResNum { return "" , nil , fmt.Errorf("wrong method %s, should have 2 output: (string,interface{})" , methodName) } if methodRes[0 ].Kind() != reflect.String { return "" , nil , fmt.Errorf("wrong method %s, first output should be string" , methodName) } key := methodRes[0 ].String() return key, methodRes[1 ], nil }
4.array,slice类型的转换 如果是array
,slice
类型,类似地,检查有没有实现传入参数的方法,如果实现了,就调用这个方法。如果没有实现,将这个field的tag作为key,域的值作为value。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 switch fieldValue.Kind() { case reflect.Slice, reflect.Array: _, ok := fieldValue.Type().MethodByName(methodName) if ok { key, value, err := callFunc(fieldValue, methodName) if err != nil { return nil , err } res[key] = value continue } res[tagVal] = fieldValue .... }
5.其他类型 对于其他类型,例如内嵌的map
,直接将其返回结果的值。
1 2 3 4 5 6 7 8 9 10 switch fieldValue.Kind() { ... case reflect.Map: res[tagVal] = fieldValue case reflect.Chan: res[tagVal] = fieldValue case reflect.Interface: res[tagVal] = fieldValue.Interface() default : }