用面向对象的方式操作 JSON 甚至还能做四则运算 JSON 库

前言
在之前实现的 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
因为功能类似,所以我参考了 tidwall 的 API 但去掉一些我觉得暂时用不上的特性,并调整了一点语法。
当前这个版本只能通过确定的 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 库的更多相关文章
- spring mvc传入参数不仅仅转换为json,还可以直接将json字符串转换为具体的java对象
1.controller层 /** * 查看主播资料 * * @return */ @RequestMapping(value = { "/actor_details" }, me ...
- SpringMvc使用FastJson做为json的转换器(注解方式)
在使用XML方式配置项目,使用fastjson做为Json转换器时通常的在XML内添加如下的配置: <mvc:message-converters register-defaults=" ...
- PHP中将对数据库的操作,封装成一个工具类以及学会使用面向对象的方式进行编程
<?php class SqlTool { //属性 private $conn; private $host="localhost"; private $user=&quo ...
- Lua和C++交互 学习记录之九:在Lua中以面向对象的方式使用C++注册的类
主要内容转载自:子龙山人博客(强烈建议去子龙山人博客完全学习一遍) 部分内容查阅自:<Lua 5.3 参考手册>中文版 译者 云风 制作 Kavcc vs2013+lua-5.3.3 在 ...
- MySQL原生API、MySQLi面向过程、MySQLi面向对象、PDO操作MySQL
[转载]http://www.cnblogs.com/52fhy/p/5352304.html 本文将举详细例子向大家展示PHP是如何使用MySQL原生API.MySQLi面向过程.MySQLi面向对 ...
- Android-Sqlite-OOP方式操作增删改查
之前写的数据库增删改查,是使用SQL语句来实现的,Google 就为Android开发人员考虑,就算不会SQL语句也能实现增删改查,所以就有了OOP面向对象的增删改查方式 其实这种OOP面向对象的增删 ...
- 在nginx启动后,如果我们要操作nginx,要怎么做呢 别增加无谓的上下文切换 异步非阻塞的方式来处理请求 worker的个数为cpu的核数 红黑树
nginx平台初探(100%) — Nginx开发从入门到精通 http://ten 众所周知,nginx性能高,而nginx的高性能与其架构是分不开的.那么nginx究竟是怎么样的呢?这一节我们先来 ...
- [转] Linux下用文件IO的方式操作GPIO(/sys/class/gpio)
点击阅读原文 一.概述 通过 sysfs 方式控制 GPIO,先访问 /sys/class/gpio 目录,向 export 文件写入 GPIO 编号,使得该 GPIO 的操作接口从内核空间暴露到用户 ...
- httpclient 认证方式访问http api/resutful api并获取json结果
最近,因公司线上环境rabbitmq经常发生堆积严重的现象,于是跟运维组讨论,帮助开发个集中监控所有rabbitmq服务器运行情况的应用,需要通过java访问rabbitmq暴露的http api并接 ...
随机推荐
- echarts踩坑总结
1,有关echarts引用的相关报错直接写这句 import * as echarts from 'echarts' 2,折线图 chartsObj = { tooltip: { trigger: ...
- 【转】python代码优化常见技巧
https://blog.csdn.net/egefcxzo3ha1x4/article/details/97844631
- 论文解读(SUBG-CON)《Sub-graph Contrast for Scalable Self-Supervised Graph Representation Learning》
论文信息 论文标题:Sub-graph Contrast for Scalable Self-Supervised Graph Representation Learning论文作者:Yizhu Ji ...
- 面试必问的8个CSS响应式单位,你知道几个?
大家好,我是半夏,一个刚刚开始写文的沙雕程序员.如果喜欢我的文章,可以关注 点赞 加我微信:frontendpicker,一起学习交流前端,成为更优秀的工程师-关注公众号:搞前端的半夏,了解更多前端知 ...
- ucore lab8 文件系统 学习笔记
最后一战果然过瘾.代码量够多,新机制够复杂度,都管饱.做这一课就像从高山上往下走,坡急且路险,还不知自己的方位,琢磨不透系统的架构.待到下了山,回头一看豁然开朗,原来方才自己所下的山是这般模样.在这里 ...
- springboot简单发送邮件介绍
1.新建一个springboot项目 2.所需要的的jar包: <dependency> <groupId>org.springframework.boot</group ...
- 老生常谈系列之Aop--AspectJ
老生常谈系列之Aop--AspectJ 这篇文章的目的是大概讲解AspectJ是什么,所以这个文章会花比较长的篇幅去解释一些概念(这对于日常开发来说没一点卵用,但我就是想写),本文主要参考Aspect ...
- python和pycharm下载与安装
python解释器 1.python的由来 Python诞生于1989年的一个圣诞节,其创作者Guido van Rossum为了打发圣诞节假期的无聊,便开始了Python语言的编写.Python第一 ...
- 初学者都能懂得 Git 说明
初学者都能懂得 Git 说明 本文写于 2020 年 8 月 10 日 网上有很多非常优秀的 Git 教程,但是他们都是面向有一定基础的开发者的. 可是对于没什么基础的初学者甚至是偶尔操作代码的设计师 ...
- 为什么列式存储会被广泛用在 OLAP 中?
大家好,我是大D. 不知是否有小伙伴们疑问,为什么列式存储会广泛地应用在 OLAP 领域,和行式存储相比,它的优势在哪里?今天我们一起来对比下这两种存储方式的差别. 其实,列式存储并不是一项新技术,最 ...