mapstructure
转载:https://darjun.github.io/2020/07/29/godailylib/mapstructure/
作用:
用于将通用的map[string]interface{}
解码到对应的 Go 结构体中,或者执行相反的操作。很多时候,解析来自多种源头的数据流时,我们一般事先并不知道他们对应的具体类型。只有读取到一些字段之后才能做出判断。这时,我们可以先使用标准的encoding/json
库将数据解码为map[string]interface{}
类型,然后根据标识字段利用mapstructure
库转为相应的 Go 结构体以便使用。
快速构建:
$ mkdir mapstructure && cd mapstructure
$ go mod init github.com/sixinshuier/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)
}
}
}
字段标签 mapstructure:"username"
type Person struct {
Name string `mapstructure:"username"`
}
内嵌结构:mapstructure:",squash"
type Friend struct {
Person `mapstructure:",squash"` // 将person的字段提到 friend上,即将结构体的字段提到父结构上
}
未映射的值:mapstructure:",remain"
如果源数据中有未映射的值(即结构体中无对应的字段),mapstructure
默认会忽略它。
我们可以在结构体中定义一个字段,为其设置mapstructure:",remain"
标签。这样未映射的值就会添加到这个字段中。注意,这个字段的类型只能为map[string]interface{}
或map[interface{}]interface{}
。```
package main
import (
"encoding/json"
"fmt"
"github.com/mitchellh/mapstructure"
"log"
)
type Person struct {
Name string
Age int
Job string
Other map[string]interface{} `mapstructure:",remain"`
}
func main() {
data := `
{
"name": "dj",
"age":18,
"job":"programmer",
"height":"1.8m",
"handsome": true
}
`
var m map[string]interface{}
err := json.Unmarshal([]byte(data), &m)
if err != nil {
log.Fatal(err)
}
var p Person
mapstructure.Decode(m, &p)
fmt.Println("other", p.Other)
}
逆向反转:mapstructure:",omitempty"
前面我们都是将map[string]interface{}
解码到 Go 结构体中。mapstructure
当然也可以将 Go 结构体反向解码为map[string]interface{}
。在反向解码时,我们可以为某些字段设置mapstructure:",omitempty"
。这样当这些字段为默认值时,就不会出现在结构的map[string]interface{}
中:
type Person struct {
Name string
Age int
Job string `mapstructure:",omitempty"`
}
func main() {
p := &Person{
Name: "dj",
Age: 18,
}
var m map[string]interface{}
mapstructure.Decode(p, &m)
data, _ := json.Marshal(m)
fmt.Println(string(data))
}
Metadata
解码时会产生一些有用的信息,mapstructure
可以使用Metadata
收集这些信息。Metadata
结构如下:
// mapstructure.go
type Metadata struct {
Keys []string
Unused []string
}
Metadata
只有两个导出字段:
Keys
:解码成功的键名;Unused
:在源数据中存在,但是目标结构中不存在的键名。
为了收集这些数据,我们需要使用DecodeMetadata
来代替Decode
方法:
type Person struct {
Name string
Age int
}
func main() {
m := map[string]interface{}{
"name": "dj",
"age": 18,
"job": "programmer",
}
var p Person
var metadata mapstructure.Metadata
mapstructure.DecodeMetadata(m, &p, &metadata)
fmt.Printf("keys:%#v unused:%#v\n", metadata.Keys, metadata.Unused)
}
返回结果:
keys:[]string{"Name", "Age"} unused:[]string{"job"}
弱类型输入:
有时候,我们并不想对结构体字段类型和map[string]interface{}
的对应键值做强类型一致的校验。这时可以使用WeakDecode/WeakDecodeMetadata
方法,它们会尝试做类型转换:
type Person struct {
Name string
Age int
Emails []string
}
func main() {
m := map[string]interface{}{
"name": 123,
"age": "18",
"emails": []int{1, 2, 3},
}
var p Person
err := mapstructure.WeakDecode(m, &p)
if err == nil {
fmt.Println("person:", p)
} else {
fmt.Println(err.Error())
}
}
解码器
除了上面介绍的方法外,mapstructure
还提供了更灵活的解码器(Decoder
)。可以通过配置DecoderConfig
实现上面介绍的任何功能:
// mapstructure.go
type DecoderConfig struct {
ErrorUnused bool
ZeroFields bool
WeaklyTypedInput bool
Metadata *Metadata
Result interface{}
TagName string
}
各个字段含义如下:
ErrorUnused
:为true
时,如果输入中的键值没有与之对应的字段就返回错误;ZeroFields
:为true
时,在Decode
前清空目标map
。为false
时,则执行的是map
的合并。用在struct
到map
的转换中;WeaklyTypedInput
:实现WeakDecode/WeakDecodeMetadata
的功能;Metadata
:不为nil
时,收集Metadata
数据;Result
:为结果对象,在map
到struct
的转换中,Result
为struct
类型。在struct
到map
的转换中,Result
为map
类型;TagName
:默认使用mapstructure
作为结构体的标签名,可以通过该字段设置。