Go 中如何解析 json 内部结构不确定的情况

本文主要介绍的是关于 Go 如何解析 json 内部结构不确定的情况。

首先,我们直接看一个来提问吧。

问题如下:

上游传递不确定的json,如何透传给下游业务?比如,我解析参数

{"test": 1,"key": {"k1": "1","k2": 2}
}

但是key 结构体下面是未知的。可能是K1 K2 K3 … KN。如何解析传递那?

对于 json 格式数据的解析,如果其中的某个成员结构不确定。

我总结一般有几种方式处理。

常见的几种方案

第一个方案,也是最容易想到的,将那个不确定的成员用 map[string]interface{} 替代。

type Data struct {Test int                    `json:"test"`Key  map[string]interface{} `json:"test"`
}

但问题是,这种方式太坑,每次从 key 中拿数据,都要做类型检查,判断是否 ok。

第二种,既然 map[string]interface{} 的方式太坑,那如果要是能用结构体就好了。

虽然其中某个成员的结构不确定,但如果共性字段比较多,如都是与人相关,那肯定都有名字,年龄之类的字段,但如果是教师和学生,就会有一些不同的字段,把所有的不同字段都包含进来即可。但如果不同字段太多,那也不是很方便。

第三种,终极解决方案,如果能先解析第一层的结构,再根据第一层的结果,确定第二层的结构,那就方便多了。不确定的成员依然用 map[string]interface{} 表示,确定结构后,再将 map[string]interface{} 解析为具体的某个结构。结构体使用起来就方便很多了。

问题最终就变成了如何将 map[string]interface{} 转化为 struct,这个过程必然会用到反射,可以自己实现。但其他人早造就想到了,一个第三方库,地址:https://github.com/mitchellh/mapstructure 。

一个实际的案例

看一个我工作遇到的一个实际案例。

我在工作中,数据库数据实时更新到 elasticsearch,在实践过程中遇到了一些 JSON 数据处理的问题。

什么样的数据呢?

实时数据获取是通过 binlog 解析推送而来的的数据,并通过消息队列 kafka 传输给处理程序。

收到的 JSON,类似如下形式。

{"type": "UPDATE","database": "blog","table": "blog","data": [{"blogId": "100001","title": "title","content": "this is a blog","uid": "1000012","state": "1"}]
}

简单说下数据的逻辑,type 表示数据库事件是新增、更新还是删除事件,database 表示对应的数据库名称,table 表示相应的表名称,data 即为数据库中数据。

怎么处理这串 JSON 呢?

json 转化为 map

最先想到的方式就是通过 json.Unmarshal 将 JSON 转化 map[string]interface{}。

示例代码:

func main () {msg := []byte(`{"type": "UPDATE","database": "blog","table": "blog","data": [{"blogId": "100001","title": "title","content": "this is a blog","uid": "1000012","state": "1"}]}`)var event map[string]interface{}if err := json.Unmarshal(msg, &event); err != nil {panic(err)}fmt.Println(event)
}

打印结果如下:

map[data:[map[title:title content:this is a blog uid:1000012 state:1 blogId:100001]] type:UPDATE database:blog table:blog]

到此,就成功解析出了数据。接下来是使用它,但我觉得 map 通常有几个不足。

  • 通过 key 获取数据,可能出现不存在的 key,为了严谨,需要检查 key 是否存在;
  • 相对于结构体的方式,map数据提取不便且不能利用 IDE 补全检查,key 容易写错;

针对这个情况,可以怎么处理呢?如果能把 JSON 转化为struct 就好了。

json 转化为 struct

在 GO 中,json 转化为 struct 也非常方便,只需提前定义好转化的 struct 即可。我们先来定义一下转化的 struct。

type Event struct {Type     string              `json:"type"`Database string              `json:"database"`Table    string              `json:"table"`Data     []map[string]string `json:"data"`
}

说明几点

  • 实际场景中,canal 消息的 data 结构是由表决定的,在 JSON 成功解析前无法提前知道,所以这里定义为 map[string]string;
  • 转化的结构体成员必须是可导出的,所以成员变量名都是大写,而与 JSON 的映射通过 json:"tagName" 的 tagName 完成。

解析代码非常简单,如下:

e := Event{}
if err := json.Unmarshal(msg, &e); err != nil {panic(err)
}fmt.Println(e)

打印结果:

{UPDATE blog blog [map[blogId:100001 title:title content:this is a blog uid:1000012 state:1]]}

接下来,数据使用就方便了不少,比如事件类型获取,通过 event.Type 即可完成。不过,要泼盆冷水,因为 data 还是 []map[string]string 类型,依然有 map 的那些问题。

能不能把 map 转化为 struct ?

map 转化为 struct

据我所知,map 转为转化为 struct,GO 是没有内置的。如果要实现,需要依赖于 GO 的反射机制。

不过,幸运的是,其实已经有人做了这件事,包名称为 mapstructure,使用也非常简单,敲一遍它提供的几个例子就学会了。README 中也说了,该库主要是遇到必须读取一部分 JSON 才能知道剩余数据结构的场景,和我的场景如此契合。

安装命令如下:

$ go get https://github.com/mitchellh/mapstructure

开始使用前,先定义 map 将转化的 struct 结构,即 blog 结构体,如下:

type Blog struct {BlogId  string `mapstructure:"blogId"`Title   string `mapstructrue:"title"`Content string `mapstructure:"content"`Uid     string `mapstructure:"uid"`State   string `mapstructure:"state"`
}

因为,接下来要用的是 mapstructure 包,所以 struct tag 标识不再是 json,而是 mapstructure。

示例代码如下:

e := Event{}
if err := json.Unmarshal(msg, &e); err != nil {panic(err)
}if e.Table == "blog" {var blogs []Blogif err := mapstructure.Decode(e.Data, &blogs); err != nil {panic(err)}fmt.Println(blogs)
}

event 的解析和前面的一样,通过 e.Table 判断是是否来自 blog 表的数据,如果是,使用 Blog 结构体解析。接下来通过 mapstructure 的 Decode 完成解析。

打印结果如下:

[{100001 title this is a blog 1000012 1}]

到此,似乎已经完成了所有工作。非也!

弱类型解析

不知道大家有没有发现一个问题,那就是 Blog 结构体中的所有成员都是 string,这应该是 canal 做的事情,所有的值类型都是 string。但实际上 blog 表中的 uid 和 state 字段其实都是 int。

理想的结构体定义应该是下面这样。

type Blog struct {BlogId  string `mapstructure:"blogId"`Title   string `mapstructrue:"title"`Content string `mapstructure:"content"`Uid     int32  `mapstructure:"uid"`State   int32  `mapstructure:"state"`
}

但是当把新的 Blog 类型代入之前的代码,会如下的错误。

panic: 2 error(s) decoding:* '[0].state' expected type 'int32', got unconvertible type 'string'
* '[0].uid' expected type 'int32', got unconvertible type 'string'

提示类型解析失败。其实,这种形式的 json 在其他一些软类型语言中也会出现。

那如何解决这个问题?提两种解决方案

  • 使用时进行转化,比如类型为 int 的数据,使用时可以用 strconv.Atoi 转化。
  • 使用 mapstructure 提供的软类型 map 转化 struct 的功能;

显然,第一种方式太 low,转化的时候还要多一步错误检查。那第二种方式如何呢?

来看示例代码,如下:

var blogs []Blog
if err := mapstructure.WeakDecode(e.Data, &blogs); err != nil {panic(err)
}fmt.Println(blogs)

其实只需要把 mapstructure 的 Decode 替换成 WeakDecode 就行了,字如其意,弱解析。如此easy。

到此,才算完成!接下来的数据处理就简单很多了。如果想学习 mapstructure 的使用,敲敲源码中例子应该差不多了。

总结

本文由一个问题引出主题,如何处理不确定结构的 json 数据,开头提出了三种可行的解决方案,三种方案是逐层递进的。最终的方式需要依赖反射实现,当然同样的问题别人早就想到了,并开发了一个第三方包,mapstructure。

最后,本文通过一个实际的案例演示了 mapstructure 的使用。

感谢阅读,希望本文对你有所帮助。

我的博文:Go 中如何解析 json 内部结构不确定的情况

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2778158.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

2024年信息管理与工业制造与自动化国际学术会议(ICIMIMA2024)

2024年信息管理与工业制造与自动化国际学术会议(ICIMIMA2024) 会议简介 2024年信息管理与工业制造及自动化国际学术会议(ICIMIMA2024)将在中国三亚举行。会议旨在为信息管理和工业工程领域的专家、学者、工程师和技术人员提供一个平台,分享…

深入Java容器:概览、设计模式与源码分析

深入Java容器:概览、设计模式与源码分析 Java 容器一、概览Collection1. Set2. List3. Queue Map 二、容器中的设计模式迭代器模式适配器模式 三、源码分析ArrayList1. 概览2. 扩容3. 删除元素4. 序列化5. Fail-Fast Vector1. 同步2. 扩容3. 与 ArrayList 的比较4. …

人工智能算法:理解其工作原理及其在现实世界中的应用

随着科技的飞速发展,人工智能(AI)已逐渐成为我们生活中不可或缺的一部分。从智能语音助手到自动驾驶汽车,再到医疗诊断系统,人工智能算法正以前所未有的速度改变着我们的世界。本文将带您深入探讨人工智能算法的工作原…

【leetcode热题100】分隔链表

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你应当 保留 两个分区中每个节点的初始相对位置。 示例 1: 输入:head [1,4,3,2,5,2], x 3 输出&am…

【开源】JAVA+Vue+SpringBoot实现班级考勤管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统基础支持模块2.2 班级学生教师支持模块2.3 考勤签到管理2.4 学生请假管理 三、系统设计3.1 功能设计3.1.1 系统基础支持模块3.1.2 班级学生教师档案模块3.1.3 考勤签到管理模块3.1.4 学生请假管理模块 3.2 数据库设…

2024年【氧化工艺】新版试题及氧化工艺操作证考试

题库来源:安全生产模拟考试一点通公众号小程序 氧化工艺新版试题是安全生产模拟考试一点通生成的,氧化工艺证模拟考试题库是根据氧化工艺最新版教材汇编出氧化工艺仿真模拟考试。2024年【氧化工艺】新版试题及氧化工艺操作证考试 1、【单选题】 对现场窨…

【GO语言卵细胞级别教程】04.GO函数介绍

【GO语言卵细胞级别教程】04.GO函数介绍 目录: 【GO语言卵细胞级别教程】04.GO函数介绍0.创建项目1.函数的引入2.注意事项3.详细介绍3.1 形参介绍 0.创建项目 创建目录 执行命令加载模块 cd 02.gostudy目录下 1.进入目录下 cd 02.gostudy 2.初始化模块变量 go …

多线程JUC:线程池原理、自定义线程池详细解析

👨‍🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习 🌌上期文章:多线程&JUC:等待唤醒机制(生产者消费者模式) 📚订阅专栏:多线程&…

「优选算法刷题」:数青蛙

一、题目 给你一个字符串 croakOfFrogs,它表示不同青蛙发出的蛙鸣声(字符串 "croak" )的组合。由于同一时间可以有多只青蛙呱呱作响,所以 croakOfFrogs 中会混合多个 “croak” 。 请你返回模拟字符串中所有蛙鸣所需不…

Minecraft的红石教程之隐形门一号

一.前言 昨天写的,哦不,今天凌晨写的CSDN太烧脑了,今天玩会儿MinecraftA-A 二.一号隐形门 1.准备的材料: 粘性活塞,木板,红石,压力板,红石火把 2.挖洞 中间挖2*3*2的洞&#xf…

第三百一十七回

文章目录 1. 概念介绍2. 实现方法2.1 hintText2.2 labelText2.3 controller 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何在输入框中处理光标"相关的内容,本章回中将介绍如何添加输入框默认值.闲话休提,让我们一起Talk Flutter吧。 1.…

基于springboot广场舞团管理系统源码和论文

随着信息技术和网络技术的飞速发展,人类已进入全新信息化时代,传统管理技术已无法高效,便捷地管理信息。为了迎合时代需求,优化管理效率,各种各样的管理系统应运而生,各行各业相继进入信息管理时代&#xf…

Java项目:19 基于SpringBoot的医院管理系统

作者主页:舒克日记 简介:Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 医院管理系统 分为三个角色 管理员、医生、病人 管理员的主要功能:系统管理、医生管理、患者管理、预约管理、病史管理、住院信息管理、管…

【OrangePi Zero2 智能家居】阿里云人脸识别方案

一、接入阿里云 二、C语言调用阿里云人脸识别接口 三、System V消息队列和POSIX 消息队列 一、接入阿里云 在之前树莓派的人脸识别方案采用了翔云平台的方案去1V1上传比对两张人脸比对,这种方案是可行,可 以继续采用。但为了接触更多了云平台方案&…

Flink基础篇|001_Flink是什么

📫 作者简介:「六月暴雪飞梨花」,专注于研究Java,就职于科技型公司后端工程师 🏆 近期荣誉:华为云云享专家、阿里云专家博主、腾讯云优秀创作者 🔥 三连支持:欢迎 ❤️关注、&#x…

网络报文处理流程

报文处理流程 WLAN网络中的数据包括管理报文和业务数据报文。管理报文必须采用CAPWAP隧道进行转发,而业务数据报文除了可以采用CAPWAP隧道转发之外,还可以采用直接转发方式和Soft-GRE转发方式。 管理报文用来传送AC与AP之间的管理数据,存在于…

再识C语言 DAY16【进制的转换 】

文章目录 前言进制的转换一、各个进制的组成二、二进制转换其他进制三。其他进制转换为二进制四.小数部分进制转换五.八进制与十进制的相互转换 总如果您发现文章有错误请与我留言,感谢 前言 本文章总结于此视频 进制的转换 一、各个进制的组成 1. 二进制&#x…

【EAI 014】Gato: A Generalist Agent

论文标题:A Generalist Agent 论文作者:Scott Reed, Konrad Zolna, Emilio Parisotto, Sergio Gomez Colmenarejo, Alexander Novikov, Gabriel Barth-Maron, Mai Gimenez, Yury Sulsky, Jackie Kay, Jost Tobias Springenberg, Tom Eccles, Jake Bruce,…

【JAVA WEB】 css背景属性 圆角矩形的绘制

目录 背景属性设置 圆角矩形 背景属性设置 背景颜色,在style中 background-color:颜色; 背景图片 background-image:url(……) 背景图片的平铺方式 background-repeat: 平铺方式 repeat 平铺(默认)no-repeat 不平铺repeat-x 水平平铺repea…

例37:爱好选择

建立一个新的EXE工程,放两个单选,两个复选框如图33。 图33 输入代码: Sub Form1_Check1_BN_Clicked(hWndForm As hWnd, hWndControl As hWnd)Text1.Text ""If Check1.Value ThenText1.Text"你喜欢" & Check1.Cap…