技巧:Go 结构体如何转换成 map[string]interface{}

本文介绍了Go语言中将结构体转成map[string]interface{}时你需要了解的“坑”,也有你需要知道的若干方法。

我们在Go语言中通常使用结构体来保存我们的数据,例如要存储用户信息,我们可能会定义如下结构体:

// UserInfo 用户信息
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不就可以了么。说干就干,代码如下:

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)
    }
}

输出:

key:name value:q1mi
key:age value:18

看起来没什么问题,但其实这里是有一个“坑”的。那就是Go语言中的json包在序列化空接口存放的数字类型(整型、浮点型等)都会序列化成float64类型。

也就是上面例子中m["age"]现在底层是一个float64了,不是个int了。我们来验证下:

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)
    }
}

输出:

key:name value:q1mi value type:string
key:age value:18 value type:float64

很显然,出现了一个意料之外的行为,我们得想办法规避掉。

反射

没办法就需要自己动手去实现了。这里使用反射遍历结构体字段的方式生成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\n", k, v, v)
}

输出:

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:

// 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\n", k, v, v)
}

structs这个包也有很多其他的使用示例,大家可以去查看文档。但是需要注意的是目前这个库已经被作者设置为只读了。

嵌套结构体转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: "q1mi", Age: 18, Profile: Profile{"双色球"}}

第三方库structs

转换嵌套结构体的代码和上面其实是一样的:

m3 := structs.Map(&u1)
for k, v := range m3 {
    fmt.Printf("key:%v value:%v value type:%T\n", k, v, v)
}

输出结果:

key:name value:q1mi value type:string
key:age value:18 value type:int
key:profile value:map[hobby:双色球] value type:map[string]interface {}

从结果来看最后嵌套字段profilemap[string]interface {},属于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{}, 8)
    queue := make([]interface{}, 0, 2)
    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\n", k, v, v)
}

输出:

key:name value:q1mi value type:string
key:age value:18 value type:int
key:hobby value:双色球 value type:string

这下我们就把嵌套的结构体转为单层的map了,但是要注意这种场景下结构体和嵌套结构体的字段就需要避免重复。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以使用反射的方式将go结构体转换成map。下面是示例代码: ```go import ( "reflect" ) type Person struct { Name string Age int } func StructToMap(obj interface{}) map[string]interface{} { t := reflect.TypeOf(obj) v := reflect.ValueOf(obj) var data = make(map[string]interface{}) for i := 0; i < t.NumField(); i++ { data[t.Field(i).Name] = v.Field(i).Interface() } return data } // 示例 person := Person{Name: "张三", Age: 20} data := StructToMap(person) fmt.Println(data) // 输出: map[Name:张三 Age:20] ``` 这段代码会将Person结构体转换成一个map,其中map的key是结构体中的字段名,value是对应字段的值。 ### 回答2: 在Go语言中,结构体map之间的转换可以通过以下两种方式实现: 1. 手动转换: 首先,定义一个结构体类型,包含需要转换的字段和对应的标签(`json`、`xml`等)。 ```go type Person struct { Name string `json:"name"` Age int `json:"age"` Gender string `json:"gender"` } ``` 然后,创建一个同名的map类型,字段名作为map的key. ```go type PersonMap map[string]interface{} ``` 最后,通过遍历结构体的字段,将字段名和对应值存入map中。 ```go func StructToMap(p Person) PersonMap { m := make(PersonMap) v := reflect.ValueOf(p) t := reflect.TypeOf(p) for i := 0; i < v.NumField(); i++ { key := t.Field(i).Tag.Get("json") value := v.Field(i).Interface() m[key] = value } return m } ``` 调用函数`StructToMap`,将结构体转换为map。 ```go func main() { p := Person{ Name: "Alice", Age: 25, Gender: "female", } m := StructToMap(p) fmt.Println(m) } ``` 输出结果: ```go map[name:Alice age:25 gender:female] ``` 2. 使用第三方库: 可以使用一些第三方库如`github.com/mitchellh/mapstructure`来实现结构体map的更复杂转换需求。 首先,使用`go get`命令安装mapstructure库。 ```shell go get github.com/mitchellh/mapstructure ``` 然后,导入该库并使用`Decode`函数将结构体转换为map。 ```go import ( "fmt" "github.com/mitchellh/mapstructure" ) type Person struct { Name string Age int Gender string } func main() { p := Person{ Name: "Alice", Age: 25, Gender: "female", } var m map[string]interface{} err := mapstructure.Decode(p, &m) if err != nil { fmt.Println(err) } fmt.Println(m) } ``` 输出结果: ```go map[Age:25 Gender:female Name:Alice] ``` 以上是两种将Go语言中的结构体转换为map的方法,可以根据具体的需求选择适合自己的方式。 ### 回答3: 在Golang中,可以通过以下几种方法将结构体转换为Map。 1. 手动转换:通过遍历结构体的字段,并逐个将字段和对应值添加到一个空的Map中。例如: ```go type Person struct { Name string Age int Gender string } func structToMap(p Person) map[string]interface{} { result := make(map[string]interface{}) result["Name"] = p.Name result["Age"] = p.Age result["Gender"] = p.Gender return result } ``` 这种方法的缺点是需要手动指定每个字段的名称和对应值,适用于结构体字段较少的情况。 2. 使用反射:通过反射库可以获取结构体的字段信息,并动态地将字段和对应值添加到Map中。例如: ```go import "reflect" func structToMap(p Person) map[string]interface{} { result := make(map[string]interface{}) structValue := reflect.ValueOf(p) structType := structValue.Type() for i := 0; i < structValue.NumField(); i++ { field := structValue.Field(i) fieldName := structType.Field(i).Name result[fieldName] = field.Interface() } return result } ``` 这种方法可以适用于任意结构体,无需手动指定字段,但是相比于手动转换会有一定的性能影响。 无论使用哪种方法,都需要根据实际需求选择适合的方式进行转换。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值