用面向对象的方式操作 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并接 ...
随机推荐
- 我向PostgreSQL社区贡献的功能:空闲会话超时
经过约八个月的努力,终于完成了 PostgreSQL 空闲会话超时断开的功能. 该功能将在版本 14 中发布. 这是我第一次向 PostgreSQL 提供功能,虽然之前也有向社区提供过补丁,但是这次整 ...
- FileNotFoundError: [Errno 2] No such file or directory: 'image/1.jpg'问题解决
FileNotFoundError: [Errno 2] No such file or directory: 'image/1.jpg'问题 最近在学习爬虫,想爬一些图片并保存到本地,但是在下载图片 ...
- 项目完成 - 基于Django3.x版本 - 开发部署小结
前言 最近因为政企部门的工作失误,导致我们的项目差点挂掉,客户意见很大,然后我们只能被动进入007加班状态,忙得嗷嗷叫,直到今天才勉强把项目改完交付,是时候写一个小结. 技术 因为前期需求不明确,数据 ...
- 攻防世界-MISC:Test-flag-please-ignore
这是攻防世界MISC高手进阶区的题目,题目如下 点击下载附件一,解压后得到一个文本文件,打开后得到一串字符串如下: 通过观察,发现是16进制的字符串(由0~f)的字符串组成,尝试将16进制转字符串,结 ...
- python数据处理-matplotlib入门(4)-条形图和直方图
摘要:先介绍条形图直方图,然后用随机数生成一系列数据,保存到列表中,最后统计出相关随机数据的概率并展示 前述介绍了由点进行划线形成的拆线图和散点形成的曲线图,连点成线,主要用到了matplotlib中 ...
- iNeuOS工业互联网操作系统,数据点、设备和业务的计算与预警
目 录 1. 概述... 2 2. 概念解释... 2 3. 数据点的计算与预警... 2 4. 设备的计算与预警... 3 5. 业务的 ...
- kubevirt在360的探索之路(k8s接管虚拟化)
来源:360云计算 KubeVirt是一个Kubernetes插件,在调度容器之余也可以调度传统的虚拟机.它通过使用自定义资源(CRD)和其它 Kubernetes 功能来无缝扩展现有的集群,以提供一 ...
- Azure DevOps (十二) 通过Azure Devops部署一个SpringBoot应用
文章配套视频专栏: https://space.bilibili.com/38649342/channel/seriesdetail?sid=2267536 视频正在努力更新. 上一篇文章中,我们通过 ...
- p2p-tunnel 打洞内网穿透系列(一)客户端配置及打洞
系列文章 p2p-tunnel 打洞内网穿透系列(一)客户端配置及打洞 p2p-tunnel 打洞内网穿透系列(二)TCP转发访问远程共享文件夹 p2p-tunnel 打洞内网穿透系列(三)TCP转发 ...
- django基础--02基于数据库的小项目
摘要:简单修改.增加部分页面,了解django开发的过程.(Python 3.9.12,django 4.0.4 ) 接前篇,通过命令: django-admin startproject myWeb ...