Golang中結(jié)構(gòu)體映射mapstructure庫(kù)深入詳解
在數(shù)據(jù)傳遞時(shí),需要先編解碼;常用的方式是JSON編解碼(參見《golang之JSON處理》)。但有時(shí)卻需要讀取部分字段后,才能知道具體類型,此時(shí)就可借助mapstructure庫(kù)了。
mapstructure庫(kù)
mapstructure可方便地實(shí)現(xiàn)map[string]interface{}與struct間的轉(zhuǎn)換;使用前,需要先導(dǎo)入庫(kù):
go get github.com/mitchellh/mapstructure
字段標(biāo)簽
默認(rèn)情況下,mapstructure使用字段的名稱做匹配映射(即在map中以字段名為鍵值查找字段值);注意匹配時(shí)是忽略大小寫的。也可通過(guò)標(biāo)簽來(lái)設(shè)定字段映射名稱:
type Person struct {
Name string `mapstructure:"userName"`
}
內(nèi)嵌結(jié)構(gòu)
go中結(jié)構(gòu)體是可以任意嵌套的;嵌套后即認(rèn)為擁有對(duì)應(yīng)的字段。但是,默認(rèn)情況下mapstructure只處理當(dāng)前結(jié)構(gòu)定義的字段,若要自動(dòng)處理內(nèi)嵌字段需要添加標(biāo)簽squash:
type Student struct {
Person `mapstructure:",squash"`
Age int
}
未映射字段
若源數(shù)據(jù)中有未映射的值(即結(jié)構(gòu)體中無(wú)對(duì)應(yīng)的字段),mapstructure默認(rèn)會(huì)忽略它??梢栽诮Y(jié)構(gòu)體中定義一個(gè)特殊字段(類型為map[string]interface{},且標(biāo)簽要設(shè)置為mapstructure:",remain"),來(lái)存放所有未能映射的字段中。
type Student struct {
Name string
Age int
Other map[string]interface{} `mapstructure:",remain"`
}
Metadata
mapstructure中可以使用Metadata收集一些解碼時(shí)會(huì)產(chǎn)生的有用信息。
// mapstructure.go
type Metadata struct {
Keys []string // 解碼成功的鍵
Unused []string // 源數(shù)據(jù)中存在,但目標(biāo)結(jié)構(gòu)中不存在的鍵
Unset []string // 未設(shè)定的(源數(shù)據(jù)中缺失的)鍵
}
為了獲取這些信息,需要使用DecodeMetadata來(lái)解碼:
var metadata mapstructure.Metadata
err := mapstructure.DecodeMetadata(m, &p, &metadata)
弱類型輸入
有時(shí)候,并不想對(duì)結(jié)構(gòu)體字段類型和map[string]interface{}的對(duì)應(yīng)鍵值做強(qiáng)類型一致的校驗(yàn)。這時(shí)可以使用WeakDecode/WeakDecodeMetadata方法,它們會(huì)嘗試做類型轉(zhuǎn)換:
- 布爾轉(zhuǎn)字符串:true = “1”, false = “0”;
- 布爾轉(zhuǎn)數(shù)字:true = 1, false = 0;
- 數(shù)字轉(zhuǎn)布爾:true if value != 0;
- 字符串轉(zhuǎn)布爾:可接受,
- 真:1, t, T, TRUE, true, True
- 假:0, f, F, FALSE, false, False
- 數(shù)字轉(zhuǎn)字符串:自動(dòng)base10轉(zhuǎn)換;
- 負(fù)數(shù)轉(zhuǎn)為無(wú)符號(hào)數(shù)(上溢);
- 字符串轉(zhuǎn)數(shù)字:根據(jù)前綴(如0x等)轉(zhuǎn)換;
- 空數(shù)組與空map間互轉(zhuǎn);
- 單個(gè)值轉(zhuǎn)為切片;
逆向轉(zhuǎn)換
除將map轉(zhuǎn)換為結(jié)構(gòu)體外,mapstructure也可以將結(jié)構(gòu)體反向解碼為map[string]interface{}。在反向解碼時(shí),我們可以為某些字段設(shè)置mapstructure:“,omitempty”,當(dāng)這些字段為默認(rèn)值時(shí),就不會(huì)出現(xiàn)在map中:
p := &Student{
Name: "Mike",
Age: 12,
}
var m map[string]interface{}
mapstructure.Decode(p, &m)
解碼器
mapstructure提供了解碼器(Decoder),可靈活方便地控制解碼:
type DecoderConfig struct {
// 若設(shè)定,則在任何解碼或類型轉(zhuǎn)換(設(shè)定了WeaklyTypedInput)前調(diào)用;對(duì)于設(shè)定了squash的內(nèi)嵌字段,整體調(diào)用一次;若返回錯(cuò)誤,則整個(gè)解碼失敗
DecodeHook DecodeHookFunc
// 若設(shè)定,則源數(shù)據(jù)中存在未使用字段時(shí),報(bào)錯(cuò)
ErrorUnused bool
// 若設(shè)定,則有字段未設(shè)定時(shí),報(bào)錯(cuò)
ErrorUnset bool
// 若設(shè)定,則在設(shè)定字段前先清空(對(duì)于map等類型會(huì)先清理掉舊數(shù)據(jù))
ZeroFields bool
// 若設(shè)定,支持若類型間的轉(zhuǎn)換
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
}一個(gè)支持弱類型轉(zhuǎn)換的示例:要獲取的結(jié)果放到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)
}
}
示例
通過(guò)一個(gè)messageData結(jié)構(gòu),action會(huì)指示最終的data類型。接收到數(shù)據(jù)后,先解析出atcion,再根據(jù)action轉(zhuǎn)換為真實(shí)的類型。
因time.Time是一個(gè)結(jié)構(gòu)體(json序列化時(shí)會(huì)轉(zhuǎn)換為時(shí)間字符串),mapstructure無(wú)法正確處理,所以推薦使用時(shí)間戳。
為了能正確解析內(nèi)嵌的DataBasic,需要標(biāo)記為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)
}
到此這篇關(guān)于Golang中結(jié)構(gòu)體映射mapstructure庫(kù)深入詳解的文章就介紹到這了,更多相關(guān)Go mapstructure內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語(yǔ)言學(xué)習(xí)教程之goroutine和通道的示例詳解
這篇文章主要通過(guò)A?Tour?of?Go中的例子進(jìn)行學(xué)習(xí),以此了解Go語(yǔ)言中的goroutine和通道,文中的示例代碼講解詳細(xì),感興趣的可以了解一下2022-09-09
Golang設(shè)計(jì)模式中的橋接模式詳細(xì)講解
橋接模式是一種結(jié)構(gòu)型設(shè)計(jì)模式,通過(guò)橋接模式可以將抽象部分和它的實(shí)現(xiàn)部分分離,本文主要介紹了GoLang橋接模式,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2023-01-01
Golang中調(diào)用deepseekr1的教程詳解
這篇文章主要為大家詳細(xì)介紹了Golang中調(diào)用deepseekr1的相關(guān)教程,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以了解下2025-02-02
Golang實(shí)現(xiàn)Md5校驗(yàn)的示例代碼
本文主要介紹了Golang實(shí)現(xiàn)Md5校驗(yàn)的示例代碼,要求接收方需要文件的md5值,和接收到的文件做比對(duì),以免文件不完整,但引起bug,下面就一起來(lái)解決一下2024-08-08

