一个Json解析库的设计和实现
一个Json解析库的设计和实现
- 一个Json解析库的设计和实现
- 设计思路
- 实现方法
- 1. 预处理(去除注释)
- 2. 词法分析
- 3. 语法分析
- 4. 树型优化
- 5. Json树构建
- 6. 后端处理
- 整体架构
一个Json解析库的设计和实现
设计思路
当前很多Json库基于状态机思想,所有Json元素均在同一个状态机中,Json文件的内容以逐字符的方式流入状态机中,促使状态流转。在这个大状态机中,每个Json元素设计为子状态机。具体表现为:为每个Json元素分别定义一个解析函数,再为Json文件设计一个入口函数。从这个入口函数出发,调用各Json元素的解析函数,各Json元素的解析函数依据状态流转递归调用其他Json元素的解析函数。
将Json元素全部放到同一个状态机中,好处是直观,细化到每个字符均可找到不同的状态流转。由于Json元素不多,格式简单,状态机的设计不会很复杂。依据划分的子状态机为每个Json元素单独实现解析函数,并允许相互递归调用减少了代码量。
但这种实现对于Json定制存在复杂性,对于某些场景,要求采用定制特殊语法的Json文件(此时的Json文件已经不是严格意义上的Json),例如加入注释、数值运算、引用等。随着定制元素的增多,状态机的设计愈发复杂。由于Json解析类似编译,采用编译的思路可实现从字符串到Json树、Json树到字符串的转换,编译前端和编译后端的架构使得增加定制元素相对直观和容易修改。
下面是采用编译思路设计的Json库流程图。
前端部分:
后端部分:
前端部分将字符串转换为Json树型结构,后端部分将Json树型结构转换为字符串。
实现方法
每个子模块分别实现特定的功能,再将结果传入下一个模块内。
1. 预处理(去除注释)
此步骤类似C编译器的预处理器。此步骤去除注释内容(可选将转义字符\n \t \r \u …的字符串表示转换为UTF8编码,删除部分空格等)。去除注释的过程采用状态机实现,依次将每个字符输入状态机,当状态为PCOMMENT、LINE_COMMENT、LEAVE_COMMENT时跳过字符:
预处理结束后,文件仍为字符串形式,流入词法分析器
2. 词法分析
Json的数据结构不是很复杂,大致如下表所示:
元素名称 | 形式 | 说明 |
---|---|---|
字符串 | “xxxx” | |
数字 | -0.1E+8 | |
对象 | {xxx: xxx} | 无序的 ‘名称:值’ 对集合 |
数组 | [xxx, xxx] | |
布尔 | true, false | |
Null |
为了描述方便,下面以Node指代一个“名称:值”对,其中Field指代“名称”,Value指代“值”。从Json格式描述可知,Json文件中的基本词法单元可定义为:(见仁见智)
词法单元 | 形式 | 说明 |
---|---|---|
字符串 | “xxxx” | |
数字 | -0.1E+8 | |
域Field | xxx: | 仅在对象中存在 |
布尔 | true, false | |
对象入口 | { | |
对象出口 | } | |
数组入口 | [ | |
数组出口 | ] | |
分隔符 | , | |
Null |
为每个词法单元分别实现其判定函数,每个判定函数的输入为json文件字符串,输出为范围[start, end], 以表示该词法单元在整个Json文件中的起始和终止offset。以文件第一个字符json[0]为起始,将json字符串输入到每个词法单元的解析函数中,只有一个解析函数可以成功,返回该词法单元的范围[start, end],然后跳过该词法单元,以json+end+1为起始将json字符串输入到每个词法单元的解析函数中,重复上述过程,直至json字符串末尾或所有解析函数均失败。如果所有解析函数均失败,则意味着词法错误,停止后续过程。
下面是string的此法单元判定过程:
数值的判定过程参见Json标准委员会ECMA-404_2nd文档的流程图,其为如下状态流转:
布尔等词法单元的判定过程此处不再赘述。
词法分析完成后,生成了一个以{type, [start, end]}为单元的数组,每个数组元素均代表一个词法单元,标识出词法类型和在全文中的范围。该数组送入语法分析模块内。
3. 语法分析
词法分析生成的数组为一维形式,仅标识了单词。单词和单词间的合法性和关系由语法分析去判定和构建。本程序拟采用LR分析法,由于Json元素关系相对简单,文法的表示不长:
定义x为对象,N为Node列表,n为Node,0为Null,f为Field,y为Value,s为字符串,z为数组,num为数组,bool为布尔,NY为数组成员列表;
x->{N}
N->N,n
N->n
N->0
n->fy
y->x
y->z
y->s
y->num
y->bool
z->[NY]
NY->NY,y
NY->y
NY->0
该文法需要根据LR分析过程做局部调整避免二义性。将空数组和空对象直接表示为:
x->{}
z->[]
同时删除N->0和NY->0两个文法。
修改后的文法如下, 其中V为起始符(根),$为终结符。
文法编号 | 文法 |
---|---|
0 | V->x$ |
1 | x->{N} |
2 | x->{ } |
3 | N->N,n |
4 | N->n |
5 | n->fy |
6 | y->x |
7 | y->z |
8 | y->s |
9 | y->num |
10 | y->bool |
11 | z->[NY] |
12 | z->[ ] |
13 | NY->NY,y |
14 | NY->y |
根据文法确定闭包和闭包内的文法,绘制闭包跳转图(LR(1)状态图).最后依据LR状态图得到LR分析表:
状态 | { | } | ] | num | , | f | s | [ | bool | $ | NY | z | y | n | N | x |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | s2 | g1 | ||||||||||||||
1 | END | |||||||||||||||
2 | s4 | s6 | g5 | g3 | ||||||||||||
3 | s7 | s8 | ||||||||||||||
4 | r2 | |||||||||||||||
5 | r4 | r4 | ||||||||||||||
6 | s15 | s13 | s12 | s16 | s14 | g11 | g9 | g10 | ||||||||
7 | r1 | |||||||||||||||
8 | s6 | g17 | ||||||||||||||
9 | r5 | r5 | ||||||||||||||
10 | r6 | r6 | ||||||||||||||
11 | r7 | r7 | ||||||||||||||
12 | r8 | r8 | ||||||||||||||
13 | r9 | r9 | ||||||||||||||
14 | r10 | r10 | ||||||||||||||
15 | s19 | s6 | g5 | g18 | ||||||||||||
16 | s28 | s21 | s26 | s25 | s29 | s27 | g20 | g24 | g22 | g23 | ||||||
17 | r3 | r3 | ||||||||||||||
18 | s30 | s8 | ||||||||||||||
19 | r2 | r2 | ||||||||||||||
20 | s31 | s32 | ||||||||||||||
21 | r12 | r12 | ||||||||||||||
22 | r14 | r14 | ||||||||||||||
23 | r6 | r6 | ||||||||||||||
24 | r7 | r7 | ||||||||||||||
25 | r8 | r8 | ||||||||||||||
26 | r9 | r9 | ||||||||||||||
27 | r10 | r10 | ||||||||||||||
28 | s34 | s6 | g5 | g18 | ||||||||||||
29 | s28 | s36 | s26 | s25 | s29 | s27 | g35 | g24 | g22 | g23 | ||||||
30 | r1 | r1 | ||||||||||||||
31 | r11 | r11 | ||||||||||||||
32 | s28 | s26 | s25 | s29 | s27 | g24 | g37 | g23 | ||||||||
33 | s38 | |||||||||||||||
34 | r2 | r2 | ||||||||||||||
35 | s39 | s32 | ||||||||||||||
36 | r12 | r12 | ||||||||||||||
37 | r13 | r13 | ||||||||||||||
38 | r1 | r1 | ||||||||||||||
39 | r11 | r11 |
准备状态栈,起始状态0入栈,依次输入词法单元,每输入一个词法单元,则对应坐标为 [栈顶状态, 词法单元] 的单元格,这些单元格存在四种表示:
- 出现sN标识:接受该输入,跳转到N状态,即状态N入栈;每个sN对应入栈一个状态,栈中的每个状态对应一个词法单元。
- 出现rX标识:接受该输入,并按照文法X归约;归约出栈若干状态,同时归约结果为一个非终结符Y(Y->xxx),归约后以Y作为输入, 查找单元格 [归约后栈顶的状态, Y] 。
- 出现gU标识:表示切换到状态U;此时接受非终结符Y,入栈状态U。
- 空:表示语法错误。
生成文法列表后,依据LR分析过程用栈回溯方式依次匹配各文法,归约生成树型结构,直至访问END单元格为止,此时栈顶为状态1,对应x,且匹配文法0:V->x.$。
4. 树型优化
语法分析生成的二叉语法树,存在大量与Json内容无关的节点,这些节点仅用于表示Json元素的关系,并且受限于二叉树结构,存在大量表示层级关系的节点。需要进一步删除冗余节点,将二叉树变为树。树型优化的工作是找到一个对象、数组的所有成员节点(存在Json元素含义的),将这些成员节点直接连入对象、数组下面,删除对象、数组下面表示元素关系、层级关系(不含Json元素含义的)的节点。这种优化过程如下图所示:
存在Json元素含义的节点种类为:
- Node 文法中表示为n
- 字符串 文法中表示为s
- 数值 文法中表示为num
- 布尔 文法中表示为bool
- Field 文法中表示为f
- 对象Object 文法中表示为s
- 数组Array 文法中表示为z
该优化的具体实现过程如下,采用先序遍历二叉语法树。从根节点开始,依次访问各子节点,如果找到存在Json元素含义的节点,如Node,则停止Node的向下遍历,将Node接入根节点中,再从Node的兄弟节点开始继续向下遍历,重复上述过程。当根节点遍历完成后,根节点下的所有存在Json元素含义的节点均直接连入根节点下面。再从根节点的子节点出发(此时的子节点全部为存在Json元素含义的节点),对于每个对象Object和数组Array类型的节点,将这些节点作为子树的根,重复上述过程,依次完成所有对象Object、数组Array的子节点的连入。重复上述过程直至整个语法树的每个节点全部访问完为止。
5. Json树构建
当树型优化完成后,该树型即为用户所需的Json树型,但这个树中的节点没有保存数值,仅给出了{type,[start,end]} 词法类型和单词范围。一般情况下,语法树树节点的数据类型也不是用户所需的Json对象类型。Json树构建需要完成两个工作:
- 从语法树节点生成Json对象,即类型转换,此时维持树型不变;
- 对于每个Json对象,依据{type,[start,end]}将单词字符串解析为数值。
第一条相对简单,仅依据节点的Node、字符串、数值、布尔、Field等类型依次创建Json对象类型,每创建一个Json对象,替换语法树中相应的节点即可。
第二条仍需要借助状态机,将字符串转换为数值。Json中存在的转换有如下情况:
转换 | 说明 |
---|---|
字符串->数值 | 整型、浮点型、精度 |
字符串->布尔 | |
字符串->字符串 | 转义字符处理 |
这些从字符串到数值的处理过程在ECMA-404_2nd文档中都有详细的状态机模型,参见其实现即可。由于词法分析已经判定了字符串的合法性,此处的解析省略合法性判定,认为其一定是合法的。
Json树构建完成后,从字符串到Json对象的解析过程就全部完成了。该过程仅为Json库的核心部分。实际使用中,用户总不能直接对树进行遍历查找元素吧。需要设计一套用户接口,实现如create、add、iterator、delete、find等接口,在接口内访问Json树并进行边界检查。这些接口此处不再赘述。
6. 后端处理
对于一个给定的Json树,将其转换为字符串的过程相对简单,思路是按照先序方式遍历Json树,第一次进入Object节点则输出’{‘字符,离开Object节点则输出’}‘,遍历子节点的依次将数值转换为字符串输出,每个子节点间输出’,'字符。Array节点与之类似。
在将字符串写入文件中时,需要注意转义字符的问题,对于字符串中的换行\n \r字符,需要输出两个字符"\\n" “\\r”,防止直接输出\n \r字符到文件导致Json格式错乱。
在将数值输出至文件时,要分别处理整型和浮点型,浮点型兼顾精度和科学计数法等表现形式。
整体架构
Json库的整体结构如下图:
该图不包含用户接口,即核心组件分为三部分:
- 读组件,完成前端处理,从字符串到Json树;
- 写组件,完成后端处理,从Json树到字符串;
- 转换组件,完成字符串转义字符处理,完成数值转换和精度处理。
具体实现参见:
gitee AJson工程
采用C++实现,代码量在1600行,模块化设计便于阅读。
参考文献
[1]. ECMA-404_2nd_edition_december_2017Json.pdf
[2]. 知乎-从零开始的 JSON 库教程 https://zhuanlan.zhihu.com/json-tutorial
[3]. 现代编译原理-C语言描述(修订版)
一个Json解析库的设计和实现相关推荐
- 深入 Go 中各个高性能 JSON 解析库
深入 Go 中各个高性能 JSON 解析库 转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com/archives/535 其实本来我是没打算 ...
- 手把手教你实现一个 JSON 解析器!
1. 背景 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.相对于另一种数据交换格式 XML,JSON 有着诸多优点.比如易读性更好,占用空间更少等. 在 ...
- gson解析天气json_几种常用JSON解析库性能比较
PS:公众号推文时间工作日早晨8点50分,周末下午3点30分,不见不散哈! 作者:飞污熊 xncoding.com/2018/01/09/java/jsons.html 本篇通过JMH来测试一下Jav ...
- C++的Json解析库:jsoncpp和boost
JSON(JavaScript Object Notation)跟xml一样也是一种数据交换格式,了解json请参考其官网http://json.org,本文不再对json做介绍,将重点介绍c++的j ...
- [转]C++的Json解析库:jsoncpp和boost
JSON(JavaScript Object Notation)跟xml一样也是一种数据交换格式,了解json请参考其官网http://json.org,本文不再对json做介绍,将重点介绍c++的j ...
- C++的Json解析库:jsoncpp
JSON(JavaScript Object Notation)跟xml一样也是一种数据交换格式,了解json请参考其官网http://json.org/,本文不再对json做介绍,将重点介绍c++的 ...
- C++的Json解析库:jsoncpp和boost .
JSON(JavaScript Object Notation)跟xml一样也是一种数据交换格式,了解json请参考其官网http://json.org/,本文不再对json做介绍,将重点介绍c++的 ...
- Android学习之Json解析库Gson
接着上一篇Volley,在使用Volley加载好数据之后,我们肯定不能直接使用这个数据,一般获取的数据都会是Json格式,所以自然而然我们要处理下Json,网络上有很多Json解析库,这里我使用Gso ...
- json解析对应的value为null_徒手撸一个JSON解析器
Java大联盟 致力于最高效的Java学习 关注 作者 | 田小波 cnblogs.com/nullllun/p/8358146.html1.背景JSON(JavaScript Object No ...
最新文章
- 上交张伟楠副教授:基于模型的强化学习算法,基本原理以及前沿进展(附视频)
- SAP MM 物料主数据MRP2 视图Rounding Value字段
- Python--音频文件分类代码
- 2.3.2便捷的电子邮件
- xcart-子分类/语言不显示
- 学数据结构,仅仅须要主要的编程体验
- opencv机器学习线性回归_机器学习(线性回归(二))
- 深度学习 《RNN模型》
- 【效率技巧】利用TI计算器的程序映射功能 kbdprgm1()~9() 简化GTC程序调试操作
- Delphi使用两种不同方法获取系统端口信息--(装载)
- sql server添加列
- C++ 的万能头文件,你知道多少?
- 最新MT6763参考设计芯片资料
- JavaWeb之servlet详解(转帖)
- 2018.5.1 差分放大电路实验
- 函数的单调性与极值点
- 咖说 | 哪怕遭受攻击,DeFi协议也很快能“春风吹又生”?
- 2019年创业做什么有前景?
- 家用计算机如何连无线网,电脑上怎么连接wifi_怎样连接自己家的wifi-win7之家
- (web前端网页制作课作业)使用HTML+CSS制作非物质文化遗产专题网页设计与实现
热门文章
- EMV 与 chip and pin
- 趣味项目:用Python代码做个月饼送给你
- springboot物流配送推荐系统 毕业设计-附源码072257
- 八皇后 C++ 递归算法和循环嵌套算法 共得到 92 种题解
- 2020-09-04 Android 应用在vivo手机安装提示风险应用处理方案
- 在OpenGL中实现Extrude 造型
- OpenStack案例研究:Binario云
- springboot 打卡功能_基于spring boot框架的公司考勤系统的研究与设计
- 服务器刷系统多少钱一台,梦幻西游:一个人搞垮一个服务器,土豪狂刷传音致系统崩溃!...
- 手机呼吸灯挺实用的,为什么很多手机没有了?你知道原因吗