前言

在之前实现的 JSON 解析器中当时只实现了将一个 JSON 字符串转换为一个 JSONObject,并没有将其映射为一个具体的 struct;如果想要获取值就需要先做断言将其转换为 map 或者是切片再来获,会比较麻烦。

	decode, err := gjson.Decode(`{"glossary":{"title":"example glossary","age":1}}`)
assert.Nil(t, err)
glossary := v["glossary"].(map[string]interface{})
assert.Equal(t, glossary["title"], "example glossary")
assert.Equal(t, glossary["age"], 1)

但其实转念一想,部分场景我们甚至我们只需要拿到 JSON 中的某个字段的值,这样还需要先声明一个 struct 会略显麻烦。

经过查询发现已经有了一个类似的库来解决该问题,https://github.com/tidwall/gjson 并且 star 数还很多(甚至名字都是一样的),说明这样的需求大家还是很强烈的。

于是我也打算增加类似的功能,使用方式如下:

最后还加上了一个四则运算的功能。

面向对象的方式操作 JSON

因为功能类似,所以我参考了 tidwallAPI 但去掉一些我觉得暂时用不上的特性,并调整了一点语法。

当前这个版本只能通过确定的 key 加上 . 点符号访问数据,如果是数组则用 [index] 的方式访问下标。

[] 符号访问数组我觉得要更符合直觉一些。

以下是一个包含多重嵌套 JSON 的访问示例:

str := `
{
"name": "bob",
"age": 20,
"skill": {
"lang": [
{
"go": {
"feature": [
"goroutine",
"channel",
"simple",
true
]
}
}
]
}
}` name := gjson.Get(str, "name")
assert.Equal(t, name.String(), "bob") age := gjson.Get(str, "age")
assert.Equal(t, age.Int(), 20) assert.Equal(t, gjson.Get(str,"skill.lang[0].go.feature[0]").String(), "goroutine")
assert.Equal(t, gjson.Get(str,"skill.lang[0].go.feature[1]").String(), "channel")
assert.Equal(t, gjson.Get(str,"skill.lang[0].go.feature[2]").String(), "simple")
assert.Equal(t, gjson.Get(str,"skill.lang[0].go.feature[3]").Bool(), true)

这样的语法使用个人觉得还是满符合直觉的,相信对使用者来说也比较简单。

返回值参考了 tidwall 使用了一个 Result 对象,它提供了多种方法可以方便的获取各种类型的数据

func (r Result) String() string
func (r Result) Bool() bool
func (r Result) Int() int
func (r Result) Float() float64
func (r Result) Map() map[string]interface{}
func (r Result) Array() *[]interface{}
func (r Result) Exists() bool

比如使用 Map()/Array() 这两个函数可以将 JSON 数据映射到 map 和切片中,当然前提是传入的语法返回的是一个合法 JSONObject 或数组。

实现原理

在实现之前需要先定义一个基本语法,主要支持以下四种用法:

  • 单个 key 的查询:Get(json,"name")
  • 嵌套查询: Get(json,"obj1.obj2.obj3.name")
  • 数组查询:Get(json,"obj.array[0]")
  • 数组嵌套查询:Get(json,"obj.array[0].obj2.obj3[1].name")

语法很简单,符合我们日常接触到语法规则,这样便可以访问到 JSON 数据中的任何一个值。

其实实现过程也不复杂,我们已经在上一文中实现将 JSON 字符串转换为一个 JSONObject 了。

这次只是额外再解析刚才定义的语法为 token,然后解析该 token 的同时再从生成好的 JSONObject 中获取数据。

最后在解析完 token 时拿到的 JSONObject 数据返回即可。


我们以这段查询代码为例:

首先第一步是对查询语法做词法分析,最终得到下图的 token

在词法分析过程中也可以做简单的语法校验;比如如果包含数组查询,并不是以 ] 符号结尾时就抛出语法错误。

接着我们遍历语法的 token。如下图所示:

每当遍历到 token 类型为 Key 时便从当前的 JSONObject 对象中获取数据,并用获取到的值替覆盖为当前的 JSONObject。

其中每当遇到 . [ ] 这样的 token 时便消耗掉,直到我们将 token 遍历完毕,这时将当前 JSONObject 返回即可。

在遍历过程中当遇到非法格式时,比如 obj_list[1.] 便会返回一个空的 JSONObject

语法校验这点其实也很容易办到,因为根据我们的语法规则,Array 中的 index 后一定紧接的是一个 EndArray,只要不是一个 EndArray 便能知道语法不合法了。

有兴趣的可以看下解析过程的源码:

https://github.com/crossoverJie/gjson/blob/cfbca51cc9bc0c77e6cb9c9ad3f964b2054b3826/json.go#L46

对 JSON 做四则运算

	str := `{"name":"bob", "age":10,"magic":10.1, "score":{"math":[1,2]}}`
result := GetWithArithmetic(str, "(age+age)*age+magic")
assert.Equal(t, result.Float(), 210.1)
result = GetWithArithmetic(str, "(age+age)*age")
assert.Equal(t, result.Int(), 200) result = GetWithArithmetic(str, "(age+age) * age + score.math[0]")
assert.Equal(t, result.Int(), 201) result = GetWithArithmetic(str, "(age+age) * age - score.math[0]")
assert.Equal(t, result.Int(), 199) result = GetWithArithmetic(str, "score.math[1] / score.math[0]")
assert.Equal(t, result.Int(), 2)

最后我还扩展了一下语法,可以支持对 JSON 数据中的整形(int、float)做四则运算,虽然这是一个小众需求,但做完我觉得还挺有意思的,目前在市面上我还没发现有类似功能的库,可能和小众需求有关。

其中核心的四则运算逻辑是由之前写的脚本解释器提供的:

https://github.com/crossoverJie/gscript



单独提供了一个函数,传入一个四则运算表达式返回计算结果。

由于上一版本还不支持 float,所以这次专门适配了一下。

限于篇幅,更多关于这个四则运算的实现逻辑会在后面继续分享。

总结

至此算是我第一次利用编译原理的知识解决了一点特定领域问题,在大学以及工作这些年一直觉得编译原理比较高深,所以内心一直是抗拒的,但经过这段时间的学习和实践慢慢的也掌握到了一点门道。

不过目前也只是冰山一角,后面的编译原理后端更是要涉及到计算机底层知识,所以依然任重而道远。

已上都是题外话,针对于这个库我也会长期维护;为了能达到生产的使用要求,尽量提高了单测覆盖率,目前是98%。

也欢迎大家使用,提 bug。

后面会继续优化,比如支持转义字符、提高性能等。

感兴趣的朋友请持续关注:

https://github.com/crossoverJie/gjson

用面向对象的方式操作 JSON 甚至还能做四则运算 JSON 库的更多相关文章

  1. spring mvc传入参数不仅仅转换为json,还可以直接将json字符串转换为具体的java对象

    1.controller层 /** * 查看主播资料 * * @return */ @RequestMapping(value = { "/actor_details" }, me ...

  2. SpringMvc使用FastJson做为json的转换器(注解方式)

    在使用XML方式配置项目,使用fastjson做为Json转换器时通常的在XML内添加如下的配置: <mvc:message-converters register-defaults=" ...

  3. PHP中将对数据库的操作,封装成一个工具类以及学会使用面向对象的方式进行编程

    <?php class SqlTool { //属性 private $conn; private $host="localhost"; private $user=&quo ...

  4. Lua和C++交互 学习记录之九:在Lua中以面向对象的方式使用C++注册的类

    主要内容转载自:子龙山人博客(强烈建议去子龙山人博客完全学习一遍) 部分内容查阅自:<Lua 5.3  参考手册>中文版 译者 云风 制作 Kavcc vs2013+lua-5.3.3 在 ...

  5. MySQL原生API、MySQLi面向过程、MySQLi面向对象、PDO操作MySQL

    [转载]http://www.cnblogs.com/52fhy/p/5352304.html 本文将举详细例子向大家展示PHP是如何使用MySQL原生API.MySQLi面向过程.MySQLi面向对 ...

  6. Android-Sqlite-OOP方式操作增删改查

    之前写的数据库增删改查,是使用SQL语句来实现的,Google 就为Android开发人员考虑,就算不会SQL语句也能实现增删改查,所以就有了OOP面向对象的增删改查方式 其实这种OOP面向对象的增删 ...

  7. 在nginx启动后,如果我们要操作nginx,要怎么做呢 别增加无谓的上下文切换 异步非阻塞的方式来处理请求 worker的个数为cpu的核数 红黑树

    nginx平台初探(100%) — Nginx开发从入门到精通 http://ten 众所周知,nginx性能高,而nginx的高性能与其架构是分不开的.那么nginx究竟是怎么样的呢?这一节我们先来 ...

  8. [转] Linux下用文件IO的方式操作GPIO(/sys/class/gpio)

    点击阅读原文 一.概述 通过 sysfs 方式控制 GPIO,先访问 /sys/class/gpio 目录,向 export 文件写入 GPIO 编号,使得该 GPIO 的操作接口从内核空间暴露到用户 ...

  9. httpclient 认证方式访问http api/resutful api并获取json结果

    最近,因公司线上环境rabbitmq经常发生堆积严重的现象,于是跟运维组讨论,帮助开发个集中监控所有rabbitmq服务器运行情况的应用,需要通过java访问rabbitmq暴露的http api并接 ...

随机推荐

  1. 我向PostgreSQL社区贡献的功能:空闲会话超时

    经过约八个月的努力,终于完成了 PostgreSQL 空闲会话超时断开的功能. 该功能将在版本 14 中发布. 这是我第一次向 PostgreSQL 提供功能,虽然之前也有向社区提供过补丁,但是这次整 ...

  2. FileNotFoundError: [Errno 2] No such file or directory: 'image/1.jpg'问题解决

    FileNotFoundError: [Errno 2] No such file or directory: 'image/1.jpg'问题 最近在学习爬虫,想爬一些图片并保存到本地,但是在下载图片 ...

  3. 项目完成 - 基于Django3.x版本 - 开发部署小结

    前言 最近因为政企部门的工作失误,导致我们的项目差点挂掉,客户意见很大,然后我们只能被动进入007加班状态,忙得嗷嗷叫,直到今天才勉强把项目改完交付,是时候写一个小结. 技术 因为前期需求不明确,数据 ...

  4. 攻防世界-MISC:Test-flag-please-ignore

    这是攻防世界MISC高手进阶区的题目,题目如下 点击下载附件一,解压后得到一个文本文件,打开后得到一串字符串如下: 通过观察,发现是16进制的字符串(由0~f)的字符串组成,尝试将16进制转字符串,结 ...

  5. python数据处理-matplotlib入门(4)-条形图和直方图

    摘要:先介绍条形图直方图,然后用随机数生成一系列数据,保存到列表中,最后统计出相关随机数据的概率并展示 前述介绍了由点进行划线形成的拆线图和散点形成的曲线图,连点成线,主要用到了matplotlib中 ...

  6. iNeuOS工业互联网操作系统,数据点、设备和业务的计算与预警

    目       录 1.      概述... 2 2.      概念解释... 2 3.      数据点的计算与预警... 2 4.      设备的计算与预警... 3 5.      业务的 ...

  7. kubevirt在360的探索之路(k8s接管虚拟化)

    来源:360云计算 KubeVirt是一个Kubernetes插件,在调度容器之余也可以调度传统的虚拟机.它通过使用自定义资源(CRD)和其它 Kubernetes 功能来无缝扩展现有的集群,以提供一 ...

  8. Azure DevOps (十二) 通过Azure Devops部署一个SpringBoot应用

    文章配套视频专栏: https://space.bilibili.com/38649342/channel/seriesdetail?sid=2267536 视频正在努力更新. 上一篇文章中,我们通过 ...

  9. p2p-tunnel 打洞内网穿透系列(一)客户端配置及打洞

    系列文章 p2p-tunnel 打洞内网穿透系列(一)客户端配置及打洞 p2p-tunnel 打洞内网穿透系列(二)TCP转发访问远程共享文件夹 p2p-tunnel 打洞内网穿透系列(三)TCP转发 ...

  10. django基础--02基于数据库的小项目

    摘要:简单修改.增加部分页面,了解django开发的过程.(Python 3.9.12,django 4.0.4 ) 接前篇,通过命令: django-admin startproject myWeb ...