Golang中结构体映射mapstructure库深入详解
作者:alwaysrun 发布时间:2024-04-26 17:34:40
在数据传递时,需要先编解码;常用的方式是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)
}
来源:https://blog.csdn.net/alwaysrun/article/details/128516027


猜你喜欢
- 不夸张地说,XML正在接管这个世界,正在成为今天一切Web服务和大多数SOA的基础。XML本身并非一种技术,而是程序设计语言,可支持开发者为
- 目录1、基础理论1.1 事务1.2 分布式事务2、分布式事务的解决方案2.1 两阶段提交/XA2.2 SAGA2.3 TCC2.4 本地消息
- 去掉数据重复 增加两个字段 alter TABLE T_Employee Add FSubCompany VARchar(20); ALTE
- 1 引言Pandas是作为Python数据分析著名的工具包,提供了多种数据选取的方法,方便实用。本文主要介绍Pandas的几种数据选取的方法
- 限制访问可以基于某种权限,某些检查或者为login视图提供不同的位置,这些实现方式大致相同。一般的方法是直接在视图的 request.use
- 本文实例讲述了python中map、any、all函数用法。分享给大家供大家参考。具体分析如下:最近想学python,就一直比较关注pyth
- asp时间加减运算 和转换问题 a=2007-07-24 2:23:15 b=2005-06-25 2:23:15 问题1 如何将a转换成2
- 本文实例讲述了python使用socket进行简单网络连接的方法。分享给大家供大家参考。具体如下:import socketprint &q
- 1.panic是什么panic() 是一个内建的函数2.panic有什么用作用:可以使用panic停止程序继续运行,所以大多数都是在demo
- 一、Pyeharts简介pyecharts 是一个用于生成 Echarts 图表的类库。用 Echarts 生成的图可视化效果很不错,pye
- 问题重现Installing golang.org/x/tools/cmd/guru FAILED Installing golang.or
- CSV means Comma Separated Values. It is plain text (ansi).The CSV (&qu
- JavaScript 有三种弹窗 Alert (只有确定按钮), Confirmation (确定,取消等按钮), Prompt (有输入对
- php的命名空间功能已经出来很久了,但是一直以来没怎么深究过,这次赶着有时间所以特意翻着手册做一个整理和总结帮助自己完善完善,原本准备一篇写
- 解释机器学习模型是一个困难的过程,因为通常大多数模型都是一个黑匣子,我们不知道模型内部发生了什么。创建不同类型的可视化有助于理解模型是如何执
- 无法打开用户默认数据库,登录失败,这也是SQL Server使用者熟悉的问题之一。在使用企业管理器、查询分析器、各类工具和应用软件的时候,只
- 用途logging模块是Python的内置模块,主要用于输出运行日志,可以灵活配置输出日志的各项信息。基本使用方法logging.basic
- 使用javascript多年,写过无数函数,今天却才真正弄明白两种函数定义的区别,真是悲剧,写下这个随笔, 以时刻提醒自己要打好基础 , 一
- 这几天看了Amy老师的用javascript实现瀑布流,我跟着把代码敲出来。发现这样写只能第一次载入时适应屏幕,以后改变窗口大小就不能做到自
- Numpy支持大量的维度数组和矩阵运算,对数组运算提供了大量的数学函数库!Numpy比Python列表更具优势,其中一个优势便是速度。在对大