Golang动态高效JSON解析技巧
JSON如今广泛用于配置和通信协议,但由于其定义的灵活性,很容易传递错误数据。本文介绍了如何使用mapstructure工具实现动态灵活的JSON数据解析,在牺牲一定性能的前提下,有效提升开发效率和容错能力。原文: Efficient JSON Data Handling: Dynamic Parsing Tips in Golang
打造无缝 Golang 体验,探索动态 JSON 解析技术,实现最佳开发实践。

在 Golang 开发领域,经常需要解析 JSON 数据。然而,如果值的类型不确定,是否有优雅的解决方案?
例如,当 JSON 字符串为 { "age":1 }
,而相应的结构体定义为字符串时,解析就会报错。
除了为结构体定义反序列化方法外,还有其他解决方案吗?今天,我将介绍另一种解决这一难题的方法。
Mapstructure 主要用于将任意 JSON 数据解码为 Go 结构。在处理 JSON 数据中的动态或不确定类型时,这将是一个强大的工具,提供了灵活的解决方案,超越了僵化结构定义的限制。
本质上讲,它擅长解析数据流,并将其映射到定义的结构中。
我们通过几个例子来探讨如何使用 mapstructure
。
# 1.常规用途
type Person struct {
Name string
Age int
Emails []string
Extra map[string]string
}
func normalDecode() {
input := map[string]interface{}{
"name": "Foo",
"age": 21,
"emails": []string{"one@gmail.com", "two@gmail.com", "three@gmail.com"},
"extra": map[string]string{
"twitter": "Foo",
},
}
var result Person
err := mapstructure.Decode(input, &result)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", result)
}
结果:
main.Person{Name:"Foo", Age:21, Emails:[]string{"one@gmail.com", "two@gmail.com", "three@gmail.com"}, Extra:map[string]string{"twitter":"Foo"}}
这种方法可能是最常用的,可以毫不费力地将 map[string]interface{}
映射到我们定义的结构。
在这里,我们并没有为每个字段指定标签,而是让 mapstructure
自动处理映射。
如果输入是 JSON 字符串,我们首先将其解析为 map[string]interface{}
格式,然后将其映射到结构中。
func jsonDecode() {
var jsonStr = `{
"name": "Foo",
"age": 21,
"gender": "male"
}`
type Person struct {
Name string
Age int
Gender string
}
m := make(map[string]interface{})
err := json.Unmarshal([]byte(jsonStr), &m)
if err != nil {
panic(err)
}
var result Person
err = mapstructure.Decode(m, &result)
if err != nil {
panic(err.Error())
}
fmt.Printf("%#v\n", result)
}
结果:
main.Person{Name:"Foo", Age:21, Gender:"male"}
# 2.嵌入式结构
mapstructure
使我们能够压缩多个嵌入式结构,并使用 squash
标记来处理。
type School struct {
Name string
}
type Address struct {
City string
}
type Person struct {
School `mapstructure:",squash"`
Address `mapstructure:",squash"`
Email string
}
func embeddedStructDecode() {
input := map[string]interface{}{
"Name": "A1",
"City": "B1",
"Email": "C1",
}
var result Person
err := mapstructure.Decode(input, &result)
if err != nil {
panic(err)
}
fmt.Printf("%s %s, %s\n", result.Name, result.City, result.Email)
}
结果:
A1, B1, C1
在这个例子中,Person
包含了 School
和 Address
的嵌入式结构,并通过使用 squash
标签实现了扁平化效果。
# 3.元数据
type Person struct {
Name string
Age int
Gender string
}
func metadataDecode() {
input := map[string]interface{}{
"name": "A1",
"age": 1,
"email": "B1",
}
var md mapstructure.Metadata
var result Person
config := &mapstructure.DecoderConfig{
Metadata: &md,
Result: &result,
}
decoder, err := mapstructure.NewDecoder(config)
if err != nil {
panic(err)
}
if err = decoder.Decode(input); err != nil {
panic(err)
}
fmt.Printf("value: %#v, keys: %#v, Unused keys: %#v, Unset keys: %#v\n", result, md.Keys, md.Unused, md.Unset)
}
结果:
value: main.Person{Name:"A1", Age:1, Gender:""}, keys: []string{"Name", "Age"}, Unused keys: []string{"email"}, Unset keys: []string{"Gender"}
从这个例子中,我们可以看到,使用元数据可以跟踪我们的结构与 map[string]interface{}
之间的差异。相同部分可以正确映射到相应的字段,而不同的部分则使用 Unused
和 Unset
来表示。
-
Unused
: map中存在但结构中没有的字段。 -
Unset
: 结构中存在但map中没有的字段。
# 4.避免空值映射
这里的用法类似于内置 json
软件包,利用 omitempty
标记来处理空值的映射。
type School struct {
Name string
}
type Address struct {
City string
}
type Person struct {
*School `mapstructure:",omitempty"`
*Address `mapstructure:",omitempty"`
Age int
Email string
}
func omitemptyDecode() {
result := &map[string]interface{}{}
input := Person{Email: "C1"}
err := mapstructure.Decode(input, &result)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", result)
}
结果:
&map[Age:0 Email:C1]
注意这里的 *School
和 *Address
都被标记为 omitempty
,即在解析过程中忽略空值。
另一方面,Age
没有使用 omitempty
标记,由于输入中没有相应的值,解析时使用了相应类型的零值,int 的零值为 0
。
type Person struct {
Name string
Age int
Other map[string]interface{} `mapstructure:",remain"`
}
func remainDataDecode() {
input := map[string]interface{}{
"name": "A1",
"age": 1,
"email": "B1",
"gender": "C1",
}
var result Person
err := mapstructure.Decode(input, &result)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", result)
}
结果:
main.Person{Name:"A1", Age:1, Other:map[string]interface {}{"email":"B1", "gender":"C1"}}
从代码中可以看出,Other
字段被标记为 remain
,意味着输入中任何不能正确映射的字段都将被放在 Other
字段中。
输出结果显示,email
和 gender
已被正确的放入 Other
。
# 5.自定义标签
type Person struct {
Name string `mapstructure:"person_name"`
Age int `mapstructure:"person_age"`
}
func tagDecode() {
input := map[string]interface{}{
"person_name": "A1",
"person_age": 1,
}
var result Person
err := mapstructure.Decode(input, &result)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", result)
}
结果:
main.Person{Name:"A1", Age:1}
在 Person
结构中,我们将 person_name
和 person_age
分别映射为 Name
和 Age
,从而在不改变结构的情况下实现了正确的解析。
# 6.弱类型解析
type Person struct {
Name string
Age int
Emails []string
}
func weaklyTypedInputDecode() {
input := map[string]interface{}{
"name": 123, // number => string
"age": "11", // string => number
"emails": map[string]interface{}{}, // empty map => empty array
}
var result Person
config := &mapstructure.DecoderConfig{
WeaklyTypedInput: true,
Result: &result,
}
decoder, err := mapstructure.NewDecoder(config)
if err != nil {
panic(err)
}
err = decoder.Decode(input)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", result)
}
结果:
main.Person{Name:"123", Age:11, Emails:[]string{}}
从代码中可以看出,输入的 name
、age
类型与 Person
结构中的 Name
、Age
类型不匹配。
email
字段尤其不走寻常路,一个是字符串数组,另一个是map。
通过自定义 DecoderConfig
并将 WeaklyTypedInput
设置为 true
,mapstructure
可以轻松解决此类弱类型解析问题。
不过,需要注意的是,并非所有问题都能得到解决,源代码也存在一定的局限性:
// - bools to string (true = "1", false = "0")
// - numbers to string (base 10)
// - bools to int/uint (true = 1, false = 0)
// - strings to int/uint (base implied by prefix)
// - int to bool (true if value != 0)
// - string to bool (accepts: 1, t, T, TRUE, true, True, 0, f, F,
// FALSE, false, False. Anything else is an error)
// - empty array = empty map and vice versa
// - negative numbers to overflowed uint values (base 10)
// - slice of maps to a merged map
// - single values are converted to slices if required. Each
// element is weakly decoded. For example: "4" can become []int{4}
// if the target type is an int slice.
# 7.错误处理
Mapstructure
提供了用户非常方便使用的错误信息。
让我们看看它在遇到错误时是如何进行提示的。
type Person struct {
Name string
Age int
Emails []string
Extra map[string]string
}
func decodeErrorHandle() {
input := map[string]interface{}{
"name": 123,
"age": "bad value",
"emails": []int{1, 2, 3},
}
var result Person
err := mapstructure.Decode(input, &result)
if err != nil {
fmt.Println(err.Error())
}
}
结果:
5 error(s) decoding:
* 'Age' expected type 'int', got unconvertible type 'string', value: 'bad value'
* 'Emails[0]' expected type 'string', got unconvertible type 'int', value: '1'
* 'Emails[1]' expected type 'string', got unconvertible type 'int', value: '2'
* 'Emails[2]' expected type 'string', got unconvertible type 'int', value: '3'
* 'Name' expected type 'string', got unconvertible type 'int', value: '123'
这里的错误信息会告诉我们每个字段的信息,以及字段内的值应如何表示,从而可以指导我们有效的解决问题。
总结
上述例子展示了 mapstructure
在有效解决实际问题、提供实用解决方案和节省开发精力方面的强大能力。
不过,从源码角度来看,该库显然广泛采用了反射技术,可能会在某些特殊情况下带来性能问题。
因此,开发人员在将 mapstructure
纳入项目时,必须全面考虑产品逻辑和使用场景。
你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!
本文由 mdnice 多平台发布
相关文章:

Golang动态高效JSON解析技巧
JSON如今广泛用于配置和通信协议,但由于其定义的灵活性,很容易传递错误数据。本文介绍了如何使用mapstructure工具实现动态灵活的JSON数据解析,在牺牲一定性能的前提下,有效提升开发效率和容错能力。原文: Efficient JSON Data Ha…...

双重检验锁
双重检验锁:设计模式中的单例模式,细分为单例模式中的懒加载模式。 单例模式 单例模式:指的是一个类只有一个对象。最简单的实现方式是设一个枚举类,只有一个对象。缺点是当对象还没有被使用时,对象就已经创建存在了…...
【RISC-V 指令集】RISC-V DSP 扩展指令集介绍(一)
前言: 本笔记是基于对RISC-V DSP扩展指令集文档总结的,《P-ext-proposal.pdf》文档的关键内容如下: 主要介绍了RISC-V的P扩展指令集及其相关细节。 首先,对P扩展指令进行了概述,并列出了其与其他扩展重复的指令。 …...
RocketMQ - CentOS 7.x 安装单机版并测试
【安装前环境准备】检查是否安装好JDK(必要):java -version查看CPU信息: # cat /proc/cpuinfo # lscpu # getconf _NPROCESSORS_ONLN # cat /sys/devices/system/cpu/online # cat /proc/interrupts | egrep -i cpu查看内存信息: # free -hm …...

[JavaWeb玩耍日记]HTML+CSS+JS快速使用
目录 一.标签 二.指定css 三.css选择器 四.超链接 五.视频与排版 六.布局测试 七.布局居中 八.表格 九.表单 十.表单项 十一.JS引入与输出 十二.JS变量,循环,函数 十三.Array与字符串方法 十四.自定义对象与JSON 十五.BOM对象 十六.获取…...

如何使用ArcGIS Pro创建最低成本路径
虽然两点之间直线最短,但是在实际运用中,还需要考虑地形、植被和土地利用类型等多种因素,需要加权计算最低成本路径,这里为大家介绍一下计算方法,希望能对你有所帮助。 数据来源 教程所使用的数据是从水经微图中下载…...

Neoverse CSS N3:实现市场领先能效的最快途径
区分老的架构 从云到边缘,Arm Neoverse 提供无与伦比的性能、效率、设计灵活性和 TCO 优势,正在颠覆传统基础设施芯片。 我们看到云和超大规模服务运营商正在推动更高的计算密度。随着 128 核心 CPU 设计上市(Microsoft Cobalt、阿里巴巴 Y…...

JavaScript实现的计时器效果
之前做过电商网站倒计时的效果,今天在倒计时的基础上,把代码修改了一下,改为计时器效果,实现了以下功能: 1.点击“开始”后,按秒计时且“开始”文字变为“停止”; 2.点击“停止”,计…...
仿函数(Functor(c++))
定义 仿函数(Functor)是一个可以像函数那样被调用的类对象。这意味着它实现了operator(),使得类的对象可以像函数那样被调用。 仿函数的主要特点 它是一个类。它重载了operator()。可以通过创建该类的对象,并像函数那样调用该对…...

智能汽车加速车规级存储应用DS2431P+TR 汽车级EEPROM 存储器IC
DS2431PT&R是一款1024位1-Wire EEPROM芯片,由四页存储区组成,每页256位。数据先被写入一个8字节暂存器中,经校验后复制到EEPROM存储器。该器件的特点是,四页存储区相互独立,可以单独进行写保护或进入EPROM仿真模式…...
js json转换成字符串
js中JSON数据转换成字符串,可以使用JSON.stringify()方法。 var obj {name: "张三", age: 18, gender: "男"}; var jsonString JSON.stringify(obj); console.log(jsonString); // 输出 {"name":"张三","age"…...
Linux笔记--基本操作指令
一、查看日期与日历 1.date指令 显示日期 #用法1:dateCST: China Standard Time时区,中国标准时间 #用法2: date 指定格式 [常用格式]: "%Y-%m-%d"(%F): 2022-07-25 "%H:%M:%S"(%T): 14:53:44 "%F %T" #用法3: date -d "-1 da…...

论文阅读:基于超像素的图卷积语义分割(图结构数据)
#Superpixel-based Graph Convolutional Network for Semantic Segmentation github链接 引言 GNN模型根据节点特征周围的边来训练节点特征,并获得最终的节点嵌入。通过利用具有不同滤波核的二维卷积对来自附近节点的信息进行整合,给定超像素方法生成的…...

记录踩过的坑-macOS下使用VS Code
目录 切换主题 安装插件 搭建Python开发环境 装Python插件 配置解释器 打开项目 打开终端 切换主题 安装插件 方法1 方法2 搭建Python开发环境 装Python插件 配置解释器 假设解释器已经通过Anaconda建好,只需要在VS Code中关联。 打开项目 打开终端...
30天JS挑战(第十四天)------数据的复制
第十四天挑战(数据的复制) 地址:https://javascript30.com/ 所有内容均上传至gitee,答案不唯一,仅代表本人思路 中文详解:https://github.com/soyaine/JavaScript30 该详解是Soyaine及其团队整理编撰的,是对源代码…...

【洛谷 P8682】[蓝桥杯 2019 省 B] 等差数列 题解(数学+排序+辗转相除法)
[蓝桥杯 2019 省 B] 等差数列 题目描述 数学老师给小明出了一道等差数列求和的题目。但是粗心的小明忘记了一部分的数列,只记得其中 N N N 个整数。 现在给出这 N N N 个整数,小明想知道包含这 N N N 个整数的最短的等差数列有几项? 输…...

Linux:kubernetes(k8s)部署CNI网络插件(4)
在上一章进行了node加入master Linux:kubernetes(k8s)node节点加入master主节点(3)-CSDN博客https://blog.csdn.net/w14768855/article/details/136420447?spm1001.2014.3001.5501 但是他们显示还是没准备好 看一下…...

docker save 命令 docker load 命令 快速复制容器
docker save 命令 docker load 命令 1、docker save 命令2、docker load 命令 1、docker save 命令 docker save 命令用于在系统上把正在使用的某个容器镜像 导出成容器镜像文件保存下载,以便在其他系统上导入这个容器镜像文件 以便快速在其他服务器上启动相同的容…...

Apache Flink连载(三十七):Flink基于Kubernetes部署(7)-Kubernetes 集群搭建-3
🏡 个人主页:IT贫道-CSDN博客 🚩 私聊博主:私聊博主加WX好友,获取更多资料哦~ 🔔 博主个人B栈地址:豹哥教你学编程的个人空间-豹哥教你学编程个人主页-哔哩哔哩视频 目录...

【机器学习】实验6,基于集成学习的 Amazon 用户评论质量预测
清华大学驭风计划课程链接 学堂在线 - 精品在线课程学习平台 (xuetangx.com) 代码和报告均为本人自己实现(实验满分),此次代码开源大家可以自行参考学习 有任何疑问或者问题,也欢迎私信博主,大家可以相互讨论交流哟…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...

MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...

图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...

QT开发技术【ffmpeg + QAudioOutput】音乐播放器
一、 介绍 使用ffmpeg 4.2.2 在数字化浪潮席卷全球的当下,音视频内容犹如璀璨繁星,点亮了人们的生活与工作。从短视频平台上令人捧腹的搞笑视频,到在线课堂中知识渊博的专家授课,再到影视平台上扣人心弦的高清大片,音…...