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

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...

通过Wrangler CLI在worker中创建数据库和表

官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解

JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...

Leetcode33( 搜索旋转排序数组)

题目表述 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能

指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...

Appium下载安装配置保姆教程(图文详解)

目录 一、Appium软件介绍 1.特点 2.工作原理 3.应用场景 二、环境准备 安装 Node.js 安装 Appium 安装 JDK 安装 Android SDK 安装Python及依赖包 三、安装教程 1.Node.js安装 1.1.下载Node 1.2.安装程序 1.3.配置npm仓储和缓存 1.4. 配置环境 1.5.测试Node.j…...

python可视化:俄乌战争时间线关键节点与深层原因

俄乌战争时间线可视化分析&#xff1a;关键节点与深层原因 俄乌战争是21世纪欧洲最具影响力的地缘政治冲突之一&#xff0c;自2022年2月爆发以来已持续超过3年。 本文将通过Python可视化工具&#xff0c;系统分析这场战争的时间线、关键节点及其背后的深层原因&#xff0c;全面…...

使用python进行图像处理—图像滤波(5)

图像滤波是图像处理中最基本和最重要的操作之一。它的目的是在空间域上修改图像的像素值&#xff0c;以达到平滑&#xff08;去噪&#xff09;、锐化、边缘检测等效果。滤波通常通过卷积操作实现。 5.1卷积(Convolution)原理 卷积是滤波的核心。它是一种数学运算&#xff0c;…...

OpenGL-什么是软OpenGL/软渲染/软光栅?

‌软OpenGL&#xff08;Software OpenGL&#xff09;‌或者软渲染指完全通过CPU模拟实现的OpenGL渲染方式&#xff08;包括几何处理、光栅化、着色等&#xff09;&#xff0c;不依赖GPU硬件加速。这种模式通常性能较低&#xff0c;但兼容性极强&#xff0c;常用于不支持硬件加速…...