java map的结构体_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:

}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。如有错误或未考虑完全的地方,望不吝赐教。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值