简介
mapstructure
用于将通用的map[string]interface{}
解码到对应的 Go 结构体中,或者执行相反的操作。很多时候,解析来自多种源头的数据流时,我们一般事先并不知道他们对应的具体类型。只有读取到一些字段之后才能做出判断。这时,我们可以先使用标准的encoding/json
库将数据解码为map[string]interface{}
类型,然后根据标识字段利用mapstructure
库转为相应的 Go 结构体以便使用。
快速使用
本文代码采用 Go Modules。
首先创建目录并初始化:
$ mkdir mapstructure && cd mapstructure
$ go mod init github.com/darjun/go-daily-lib/mapstructure
下载mapstructure
库:
$ go get github.com/mitchellh/mapstructure
使用:
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/mitchellh/mapstructure"
)
type Person struct {
Name string
Age int
Job string
}
type Cat struct {
Name string
Age int
Breed string
}
func main() {
datas := []string{`
{
"type": "person",
"name":"dj",
"age":18,
"job": "programmer"
}
`,
`
{
"type": "cat",
"name": "kitty",
"age": 1,
"breed": "Ragdoll"
}
`,
}
for _, data := range datas {
var m map[string]interface{}
err := json.Unmarshal([]byte(data), &m)
if err != nil {
log.Fatal(err)
}
switch m["type"].(string) {
case "person":
var p Person
mapstructure.Decode(m, &p)
fmt.Println("person", p)
case "cat":
var cat Cat
mapstructure.Decode(m, &cat)
fmt.Println("cat", cat)
}
}
}
运行结果:
$ go run main.go
person {dj 18 programmer}
cat {kitty 1 Ragdoll}
我们定义了两个结构体Person
和Cat
,他们的字段有些许不同。现在,我们约定通信的 JSON 串中有一个type
字段。当type
的值为person
时,该 JSON 串表示的是Person
类型的数据。当type
的值为cat
时,该 JSON 串表示的是Cat
类型的数据。
上面代码中,我们先用json.Unmarshal
将字节流解码为map[string]interface{}
类型。然后读取里面的type
字段。根据type
字段的值,再使用mapstructure.Decode
将该 JSON 串分别解码为Person
和Cat
类型的值,并输出。
实际上,Google Protobuf 通常也使用这种方式。在协议中添加消息 ID 或全限定消息名。接收方收到数据后,先读取协议 ID 或全限定消息名。然后调用 Protobuf 的解码方法将其解码为对应的Message
结构。从这个角度来看,mapstructure
也可以用于网络消息解码,如果你不考虑性能的话😄。
字段标签
默认情况下,mapstructure
使用结构体中字段的名称做这个映射,例如我们的结构体有一个Name
字段,mapstructure
解码时会在map[string]interface{}
中查找键名name
。注意,这里的name
是大小写不敏感的!
type Person struct {
Name string
}
当然,我们也可以指定映射的字段名。为了做到这一点,我们需要为字段设置mapstructure
标签。例