文章目录

mapstructure库字段标签内嵌结构未映射字段Metadata弱类型输入逆向转换

解码器示例

在数据传递时,需要先编解码;常用的方式是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)

}

好文推荐

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: