Golang自定义结构体转map的操作
在golang中,如何将一个结构体转成map? 本文介绍两种方法。第一种是是使用json包解析解码编码。第二种是使用反射,使用反射的效率比较高,。如果觉得代码有用,可以给我的代码仓库一个star。
假设有下面的一个结构体
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"` // string github githubpage `map:"github,dive,omitempty"` // struct dive nodive structnodive `map:"no_dive,omitempty"` // no dive struct myprofile profile `map:"my_profile,omitempty"` // struct implements its own method } 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"` } // its own tomap method func (p profile) structtomap() (key string, value interface{}) { return "time", p.date.format(timelayout) }
json包的marshal,unmarshal
先将结构体序列化成[]byte数组,再从[]byte数组序列化成结构体。
data, _ := json.marshal(&user) m := make(map[string]interface{}) json.unmarshal(data, &m)
优势
使用简单 劣势
效率比较慢
不能支持一些定制的键,也不能支持一些定制的方法,例如将struct的域展开等。
使用反射
本文实现了使用反射将结构体转成map的方法。通过标签(tag)和反射,将上文示例的newuser()返回的结果转化成下面的一个map。
其中包含struct的域的展开,定制化struct的方法。
map[string]interface{}{ "name": "user", "no_dive": structnodive{nodive: 1}, // dive struct field "url": "https://github.com/liangyaopei", "star": 1, // customized method "time": "2020-07-21 12:00:00", }
实现思路 & 源码解析
1.标签识别。
使用readtag方法读取域(field)的标签,如果没有标签,使用域的名字。然后读取tag中的选项。目前支持3个选项
'-':忽略当前这个域
'omitempty' : 当这个域的值为空,忽略这个域
'dive' : 递归地遍历这个结构体,将所有字段作为键
如果选中了一个选项,就讲这个域对应的二进制位置为1.。
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 // no tag, use field name 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。
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。
for i := 0; i < t.numfield(); i++ { fieldtype := t.field(i) // ignore unexported field if fieldtype.pkgpath != "" { continue } // read tag tagval, flag := readtag(fieldtype, tag) if flag&flagignore != 0 { continue } fieldvalue := v.field(i) if flag&flagomiempty != 0 && fieldvalue.iszero() { continue } // ignore nil pointer in field if fieldvalue.kind() == reflect.ptr && fieldvalue.isnil() { continue } if fieldvalue.kind() == reflect.ptr { fieldvalue = fieldvalue.elem() } // get kind 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 } // recursive 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: } } ... } // call function 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。
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,直接将其返回结果的值。
switch fieldvalue.kind() { ... case reflect.map: res[tagval] = fieldvalue case reflect.chan: res[tagval] = fieldvalue case reflect.interface: res[tagval] = fieldvalue.interface() default: }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。如有错误或未考虑完全的地方,望不吝赐教。