当前位置: 首页 > news >正文

Golang动态高效JSON解析技巧

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

打造无缝 Golang 体验,探索动态 JSON 解析技术,实现最佳开发实践。

Cristian Palmer @Unsplash
Cristian Palmer @Unsplash

在 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 包含了 SchoolAddress 的嵌入式结构,并通过使用 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{} 之间的差异。相同部分可以正确映射到相应的字段,而不同的部分则使用 UnusedUnset 来表示。

  • 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 字段中。

输出结果显示,emailgender 已被正确的放入 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_nameperson_age 分别映射为 NameAge,从而在不改变结构的情况下实现了正确的解析。

# 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{}}

从代码中可以看出,输入的 nameage 类型与 Person 结构中的 NameAge 类型不匹配。

email 字段尤其不走寻常路,一个是字符串数组,另一个是map。

通过自定义 DecoderConfig 并将 WeaklyTypedInput 设置为 truemapstructure 可以轻松解决此类弱类型解析问题。

不过,需要注意的是,并非所有问题都能得到解决,源代码也存在一定的局限性:

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

用CUDA C++手搓LeNet推理引擎:从PyTorch导出权重到GPU加速的完整避坑指南

用CUDA C手搓LeNet推理引擎:从PyTorch导出权重到GPU加速的完整避坑指南在深度学习模型部署的最后一公里,将训练好的模型高效移植到生产环境是每个开发者必须面对的挑战。本文将带您深入实践,从PyTorch训练好的LeNet模型出发,完整实…...

Windows远程桌面免费解锁指南:家庭版也能享受多用户并发连接

Windows远程桌面免费解锁指南:家庭版也能享受多用户并发连接 【免费下载链接】rdpwrap RDP Wrapper Library 项目地址: https://gitcode.com/gh_mirrors/rd/rdpwrap 你是否曾经因为Windows家庭版无法使用远程桌面而烦恼?或者需要多人同时访问同一…...

ESXi 6.7性能调优第一步:别急着装系统,先搞定主板BIOS里这4个关键设置

ESXi 6.7性能调优实战:BIOS层四大核心参数深度解析当你以为ESXi的性能瓶颈在于内存分配或存储配置时,可能忽略了最底层的硬件虚拟化支持。我曾亲眼见证一个中型企业的vSphere集群在调整BIOS参数后,虚拟机密度提升了40%,而硬件配置…...

破解特征相关性难题:MVIM与CVIM如何提供更稳健的变量重要性评估

1. 项目概述:从“黑盒”到“可解释”的桥梁在数据科学和机器学习的日常工作中,我们常常面临一个核心矛盾:一方面,以XGBoost、深度神经网络为代表的复杂模型因其卓越的预测性能而备受青睐;另一方面,这些模型…...

小电视空降助手:终极B站广告跳过插件完整指南

小电视空降助手:终极B站广告跳过插件完整指南 【免费下载链接】BilibiliSponsorBlock 一款跳过小电视视频中恰饭片段的浏览器插件,移植自 SponsorBlock。A browser extension to skip sponsored segments in videos, ported from the SponsorBlock 项目…...

别再让WSL2吃光你的C盘!手把手教你迁移到D盘并优化内存配置(Windows10/11通用)

WSL2系统迁移与性能调优全指南:释放C盘空间与提升运行效率 每次打开资源管理器看到C盘剩余空间不足10%的红色警告,作为开发者的你是否感到一阵窒息?WSL2虽然为Windows带来了原生的Linux体验,但默认安装配置却可能成为系统资源的&q…...

保姆级教程:在Ubuntu 22.04上从源码编译COLMAP 3.9(含6个常见Bug解决方案)

在Ubuntu 22.04上从源码编译COLMAP 3.9的终极避坑指南三维重建技术正在重塑数字世界的构建方式,而COLMAP作为开源领域的标杆工具,其强大的多视图几何算法让学术研究和工业应用都受益匪浅。但当你第一次尝试在Ubuntu系统上编译这个工具时,可能…...

Keil MDK中自定义CMSIS代码模板实战指南

1. 自定义CMSIS用户代码模板的完整指南作为一名嵌入式开发老手,我经常需要在Keil MDK环境中创建各种RTOS任务模板。官方提供的模板虽然好用,但实际项目中我们往往需要根据公司编码规范或特定硬件平台定制专属模板。今天我就来分享如何在CMSIS环境中添加自…...

量子多体系统模拟:MPS与DMRG算法实践

1. 量子多体系统模拟基础框架在量子多体系统的研究中,矩阵乘积态(MPS)已成为描述一维强关联系统的标准工具。这种表示方法的核心思想是将一个N体量子态分解为N个局部张量的收缩形式,每个张量对应一个物理位点。具体数学表达为: [ |ψ⟩ \sum…...

ARTX实时操作系统任务监控与调试实践

1. 实时任务监控需求解析在嵌入式实时操作系统(RTOS)开发中,任务调度监控是调试复杂系统的关键手段。ARTX-166作为一款面向C166架构的高级实时操作系统,其任务调度机制直接影响系统实时性能。当系统出现响应延迟或死锁时&#xff…...