一个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入栈,依次输入词法单元,每输入一个词法单元,则对应坐标为 [栈顶状态, 词法单元] 的单元格,这些单元格存在四种表示:

  1. 出现sN标识:接受该输入,跳转到N状态,即状态N入栈;每个sN对应入栈一个状态,栈中的每个状态对应一个词法单元
  2. 出现rX标识:接受该输入,并按照文法X归约;归约出栈若干状态,同时归约结果为一个非终结符Y(Y->xxx),归约后以Y作为输入, 查找单元格 [归约后栈顶的状态, Y] 。
  3. 出现gU标识:表示切换到状态U;此时接受非终结符Y,入栈状态U。
  4. 空:表示语法错误。

生成文法列表后,依据LR分析过程用栈回溯方式依次匹配各文法,归约生成树型结构,直至访问END单元格为止,此时栈顶为状态1,对应x,且匹配文法0:V->x.$。

4. 树型优化


语法分析生成的二叉语法树,存在大量与Json内容无关的节点,这些节点仅用于表示Json元素的关系,并且受限于二叉树结构,存在大量表示层级关系的节点。需要进一步删除冗余节点,将二叉树变为树。树型优化的工作是找到一个对象、数组的所有成员节点(存在Json元素含义的),将这些成员节点直接连入对象、数组下面,删除对象、数组下面表示元素关系、层级关系(不含Json元素含义的)的节点。这种优化过程如下图所示:

存在Json元素含义的节点种类为:

  1. Node 文法中表示为n
  2. 字符串 文法中表示为s
  3. 数值 文法中表示为num
  4. 布尔 文法中表示为bool
  5. Field 文法中表示为f
  6. 对象Object 文法中表示为s
  7. 数组Array 文法中表示为z

该优化的具体实现过程如下,采用先序遍历二叉语法树。从根节点开始,依次访问各子节点,如果找到存在Json元素含义的节点,如Node,则停止Node的向下遍历,将Node接入根节点中,再从Node的兄弟节点开始继续向下遍历,重复上述过程。当根节点遍历完成后,根节点下的所有存在Json元素含义的节点均直接连入根节点下面。再从根节点的子节点出发(此时的子节点全部为存在Json元素含义的节点),对于每个对象Object和数组Array类型的节点,将这些节点作为子树的根,重复上述过程,依次完成所有对象Object、数组Array的子节点的连入。重复上述过程直至整个语法树的每个节点全部访问完为止。

5. Json树构建


当树型优化完成后,该树型即为用户所需的Json树型,但这个树中的节点没有保存数值,仅给出了{type,[start,end]} 词法类型和单词范围。一般情况下,语法树树节点的数据类型也不是用户所需的Json对象类型。Json树构建需要完成两个工作:

  1. 从语法树节点生成Json对象,即类型转换,此时维持树型不变;
  2. 对于每个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库的整体结构如下图:

该图不包含用户接口,即核心组件分为三部分:

  1. 读组件,完成前端处理,从字符串到Json树;
  2. 写组件,完成后端处理,从Json树到字符串;
  3. 转换组件,完成字符串转义字符处理,完成数值转换和精度处理。

具体实现参见:
gitee AJson工程

采用C++实现,代码量在1600行,模块化设计便于阅读。


参考文献
[1]. ECMA-404_2nd_edition_december_2017Json.pdf
[2]. 知乎-从零开始的 JSON 库教程 https://zhuanlan.zhihu.com/json-tutorial
[3]. 现代编译原理-C语言描述(修订版)

一个Json解析库的设计和实现相关推荐

  1. 深入 Go 中各个高性能 JSON 解析库

    深入 Go 中各个高性能 JSON 解析库 转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com/archives/535 其实本来我是没打算 ...

  2. 手把手教你实现一个 JSON 解析器!

    1. 背景 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.相对于另一种数据交换格式 XML,JSON 有着诸多优点.比如易读性更好,占用空间更少等. 在 ...

  3. gson解析天气json_几种常用JSON解析库性能比较

    PS:公众号推文时间工作日早晨8点50分,周末下午3点30分,不见不散哈! 作者:飞污熊 xncoding.com/2018/01/09/java/jsons.html 本篇通过JMH来测试一下Jav ...

  4. C++的Json解析库:jsoncpp和boost

    JSON(JavaScript Object Notation)跟xml一样也是一种数据交换格式,了解json请参考其官网http://json.org,本文不再对json做介绍,将重点介绍c++的j ...

  5. [转]C++的Json解析库:jsoncpp和boost

    JSON(JavaScript Object Notation)跟xml一样也是一种数据交换格式,了解json请参考其官网http://json.org,本文不再对json做介绍,将重点介绍c++的j ...

  6. C++的Json解析库:jsoncpp

    JSON(JavaScript Object Notation)跟xml一样也是一种数据交换格式,了解json请参考其官网http://json.org/,本文不再对json做介绍,将重点介绍c++的 ...

  7. C++的Json解析库:jsoncpp和boost .

    JSON(JavaScript Object Notation)跟xml一样也是一种数据交换格式,了解json请参考其官网http://json.org/,本文不再对json做介绍,将重点介绍c++的 ...

  8. Android学习之Json解析库Gson

    接着上一篇Volley,在使用Volley加载好数据之后,我们肯定不能直接使用这个数据,一般获取的数据都会是Json格式,所以自然而然我们要处理下Json,网络上有很多Json解析库,这里我使用Gso ...

  9. json解析对应的value为null_徒手撸一个JSON解析器

      Java大联盟 致力于最高效的Java学习 关注 作者 | 田小波 cnblogs.com/nullllun/p/8358146.html1.背景JSON(JavaScript Object No ...

最新文章

  1. 上交张伟楠副教授:基于模型的强化学习算法,基本原理以及前沿进展(附视频)
  2. SAP MM 物料主数据MRP2 视图Rounding Value字段
  3. Python--音频文件分类代码
  4. 2.3.2便捷的电子邮件
  5. xcart-子分类/语言不显示
  6. 学数据结构,仅仅须要主要的编程体验
  7. opencv机器学习线性回归_机器学习(线性回归(二))
  8. 深度学习 《RNN模型》
  9. 【效率技巧】利用TI计算器的程序映射功能 kbdprgm1()~9() 简化GTC程序调试操作
  10. Delphi使用两种不同方法获取系统端口信息--(装载)
  11. sql server添加列
  12. C++ 的万能头文件,你知道多少?
  13. 最新MT6763参考设计芯片资料
  14. JavaWeb之servlet详解(转帖)
  15. 2018.5.1 差分放大电路实验
  16. 函数的单调性与极值点
  17. 咖说 | 哪怕遭受攻击,DeFi协议也很快能“春风吹又生”?
  18. 2019年创业做什么有前景?
  19. 家用计算机如何连无线网,电脑上怎么连接wifi_怎样连接自己家的wifi-win7之家
  20. (web前端网页制作课作业)使用HTML+CSS制作非物质文化遗产专题网页设计与实现

热门文章

  1. EMV 与 chip and pin
  2. 趣味项目:用Python代码做个月饼送给你
  3. springboot物流配送推荐系统 毕业设计-附源码072257
  4. 八皇后 C++ 递归算法和循环嵌套算法 共得到 92 种题解
  5. 2020-09-04 Android 应用在vivo手机安装提示风险应用处理方案
  6. 在OpenGL中实现Extrude 造型
  7. OpenStack案例研究:Binario云
  8. springboot 打卡功能_基于spring boot框架的公司考勤系统的研究与设计
  9. 服务器刷系统多少钱一台,梦幻西游:一个人搞垮一个服务器,土豪狂刷传音致系统崩溃!...
  10. 手机呼吸灯挺实用的,为什么很多手机没有了?你知道原因吗