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) 代码和报告均为本人自己实现(实验满分),此次代码开源大家可以自行参考学习 有任何疑问或者问题,也欢迎私信博主,大家可以相互讨论交流哟…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...

nnUNet V2修改网络——暴力替换网络为UNet++
更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...