Golang中结构体映射mapstructure库深入详解
作者:alwaysrun
在数据传递时,需要先编解码;常用的方式是JSON编解码(参见《golang之JSON处理》)。但有时却需要读取部分字段后,才能知道具体类型,此时就可借助mapstructure库了。
mapstructure库
mapstructure可方便地实现map[string]interface{}
与struct
间的转换;使用前,需要先导入库:
go get github.com/mitchellh/mapstructure
字段标签
默认情况下,mapstructure使用字段的名称做匹配映射(即在map中以字段名为键值查找字段值);注意匹配时是忽略大小写的。也可通过标签来设定字段映射名称:
type Person struct { Name string `mapstructure:"userName"` }
内嵌结构
go中结构体是可以任意嵌套的;嵌套后即认为拥有对应的字段。但是,默认情况下mapstructure只处理当前结构定义的字段,若要自动处理内嵌字段需要添加标签squash
:
type Student struct { Person `mapstructure:",squash"` Age int }
未映射字段
若源数据中有未映射的值(即结构体中无对应的字段),mapstructure默认会忽略它。可以在结构体中定义一个特殊字段(类型为map[string]interface{}
,且标签要设置为mapstructure:",remain"
),来存放所有未能映射的字段中。
type Student struct { Name string Age int Other map[string]interface{} `mapstructure:",remain"` }
Metadata
mapstructure中可以使用Metadata收集一些解码时会产生的有用信息。
// mapstructure.go type Metadata struct { Keys []string // 解码成功的键 Unused []string // 源数据中存在,但目标结构中不存在的键 Unset []string // 未设定的(源数据中缺失的)键 }
为了获取这些信息,需要使用DecodeMetadata来解码:
var metadata mapstructure.Metadata
err := mapstructure.DecodeMetadata(m, &p, &metadata)
弱类型输入
有时候,并不想对结构体字段类型和map[string]interface{}
的对应键值做强类型一致的校验。这时可以使用WeakDecode/WeakDecodeMetadata方法,它们会尝试做类型转换:
- 布尔转字符串:true = “1”, false = “0”;
- 布尔转数字:true = 1, false = 0;
- 数字转布尔:true if value != 0;
- 字符串转布尔:可接受,
- 真:1, t, T, TRUE, true, True
- 假:0, f, F, FALSE, false, False
- 数字转字符串:自动base10转换;
- 负数转为无符号数(上溢);
- 字符串转数字:根据前缀(如0x等)转换;
- 空数组与空map间互转;
- 单个值转为切片;
逆向转换
除将map转换为结构体外,mapstructure也可以将结构体反向解码为map[string]interface{}
。在反向解码时,我们可以为某些字段设置mapstructure:“,omitempty”,当这些字段为默认值时,就不会出现在map中:
p := &Student{ Name: "Mike", Age: 12, } var m map[string]interface{} mapstructure.Decode(p, &m)
解码器
mapstructure提供了解码器(Decoder),可灵活方便地控制解码:
type DecoderConfig struct { // 若设定,则在任何解码或类型转换(设定了WeaklyTypedInput)前调用;对于设定了squash的内嵌字段,整体调用一次;若返回错误,则整个解码失败 DecodeHook DecodeHookFunc // 若设定,则源数据中存在未使用字段时,报错 ErrorUnused bool // 若设定,则有字段未设定时,报错 ErrorUnset bool // 若设定,则在设定字段前先清空(对于map等类型会先清理掉旧数据) ZeroFields bool // 若设定,支持若类型间的转换 WeaklyTypedInput bool // Squash will squash embedded structs. Squash bool // Metadata is the struct that will contain extra metadata about // the decoding. If this is nil, then no metadata will be tracked. Metadata *Metadata // Result is a pointer to the struct that will contain the decoded // value. Result interface{} // The tag name that mapstructure reads for field names. This // defaults to "mapstructure" TagName string // IgnoreUntaggedFields ignores all struct fields without explicit // TagName, comparable to `mapstructure:"-"` as default behaviour. IgnoreUntaggedFields bool // MatchName is the function used to match the map key to the struct // field name or tag. Defaults to `strings.EqualFold`. This can be used // to implement case-sensitive tag values, support snake casing, etc. MatchName func(mapKey, fieldName string) bool }
一个支持弱类型转换的示例:要获取的结果放到config的result中
Name string Age int } func decoderConfig() { m := map[string]interface{}{ "name": 123, "age": "12", "job": "programmer", } var p Person var metadata mapstructure.Metadata decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ WeaklyTypedInput: true, Result: &p, Metadata: &metadata, }) if err != nil { log.Fatal(err) } err = decoder.Decode(m) if err == nil { log.Printf("Result: %#v", p) log.Printf("keys:%#v, unused:%#v\n", metadata.Keys, metadata.Unused) } else { log.Println("decode fail:", err) } }
示例
通过一个messageData结构,action会指示最终的data类型。接收到数据后,先解析出atcion,再根据action转换为真实的类型。
因time.Time是一个结构体(json序列化时会转换为时间字符串),mapstructure无法正确处理,所以推荐使用时间戳。
为了能正确解析内嵌的DataBasic,需要标记为squash。
import "github.com/mitchellh/mapstructure" type DataBasic struct { DataId string `json:"dataId"` UpdateTime int64 `json:"updateTime"` } type AddedData struct { DataBasic `mapstructure:",squash"` Tag string `json:"tag"` AddParams map[string]any `json:"addParams"` } type messageData struct { Action int `json:"action"` SeqId uint64 `json:"seqId"` Data any `json:"data"` } func decodeData() { add := &AddedData{ DataBasic: DataBasic{ DataId: "a2", UpdateTime: time.Now().UnixMilli(), }, Tag: "tag", AddParams: map[string]any{"dataId": "c2", "otherId": "t2"}, } data := &messageData{ Action: 1, Data: add, } js, err := json.Marshal(data) if err != nil { log.Printf("marshal fail: %v", err) return } got := &messageData{} err = json.Unmarshal(js, got) if err != nil { log.Printf("unmarshal fail: %v", err) return } param := new(AddedData) err = mapstructure.Decode(got.Data, param) if err != nil { log.Printf("unmarshal fail: %v", err) return } log.Printf("param: %+v", param) }
到此这篇关于Golang中结构体映射mapstructure库深入详解的文章就介绍到这了,更多相关Go mapstructure内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!