资讯详情

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

  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语言描述(修订版)

标签: gp2s29反射式传感器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台