<注>寒假和小伙伴想试着学做项目,主要学习的是腾讯的开源JSON框架RapidJson。然而在学习rapidjson之前首先还要了解JSON是什么,它都有哪些功能等。通过学习“从零开始的 JSON 库教程”后逐渐掌握了解,下为学习记录整理。
学习资料:从零开始的 JSON 库教程:https://github.com/miloyip/json-tutorial
一、JSON相关定义
1.JSON定义:
JSON(JavaScriptObject Notation, JS 对象简谱) 是一种轻量级的数据交换文本格式,采用完全独立于编程语言的文本格式来存储和表示数据。
2.JSON的7种数据结构:
{null,true,false,number,string,array,object}
3.JSON的3个需求:
1)解析(parse):JSON文本解析为树状数据结构;
2)生成(stringify):将数据结构转换成JSON文本;
3)接口(access):提供接口访问数据.
4.JSON的2个API函数:
1)解析JSON函数
2)访问结果函数
5.JSON的语法子集:
1)JSON-text = ws value ws
//JSON文本由空白(whitespace)值(value)空白(whitespace)组成
2)ws = *(%x20 / %x09 / %x0A / %x0D)
// * 表示可以为零或多个;即ws为零或多个空格符(space U+0020)、制表符(tab U+0009)、换行符(LF U+000A)、回车符(CR U+000D)构成。
3)value = null/true/false
6.JSON的3个错误码(即不符合JSON-text格式)
1)若一个 JSON 只含有空白,传回 LEPT_PARSE_EXPECT_VALUE。
2)若一个值之后,在空白之后还有其他字符,传回 LEPT_PARSE_ROOT_NOT_SINGULAR。
3)若值不是那三种字面值,传回 LEPT_PARSE_INVALID_VALUE。
二、JSON解析
1.lept_parse_value:
利用switch函数,不同的开头case指向7个解析函数
switch (*c->json) {
case 't': return lept_parse_literal(c, v, "true", LEPT_TRUE);
case 'f': return lept_parse_literal(c, v, "false", LEPT_FALSE);
case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL);
default: return lept_parse_number(c, v);
case '"': return lept_parse_string(c, v);
case '[': return lept_parse_array(c, v);
case '{': return lept_parse_object(c, v);
case '\0': return LEPT_PARSE_EXPECT_VALUE;
}
2.null/true/false解析(lept_parse_literal):
提取null、true、false进行对比,表述不完全则返回错误。以下为单独解析和整合之后进行解析,可通过阅读代码感受解析过程。
首先以单独解析true为例,了解解析过程----逐一比对。
static int lept_parse_true(lept_context* c, lept_value* v) {
EXPECT(c, 't');
if (c->json[0] != 'r' || c->json[1] != 'u' || c->json[2] != 'e')
return LEPT_PARSE_INVALID_VALUE;
c->json += 3;
v->type = LEPT_TRUE;
return LEPT_PARSE_OK;
}
将true/false/null三个整合,循环进行比对。
static int lept_parse_literal(lept_context* c,
lept_value* v, const char* literal, lept_type type)
{
size_t i;
EXPECT(c, literal[0]);
for (i = 0; literal[i + 1]; i++)
if (c->json[i] != literal[i + 1])
return LEPT_PARSE_INVALID_VALUE;
c->json += i;
v->type = type;
return LEPT_PARSE_OK;
}
3.number解析(lept_parse_number):
选择以double类型存储数字,所以首先提取double类型数字。由于数字0-9都可以表示数字,所以采用默认default处理。
default: return lept_parse_number(c, v);
因为数字是无尽的,所以在解析数字时则判断这个数字是否合法,错误则返回该错误类型,无错误解析成功。
Number一般以十进制表示,由负号(正号不表示)、整数、小数、指数组成,指数以科学计数法表示。以下为错误类型分析:
1)整数部分:若以0开头,则只可以为单个0,即0123这种表示不合法;1-9开头后面可跟任意数字
2)小数部分:小数点前后必须有一个或多个数字,只可以有一个小数点。
3)指数部分:E/e前后必须有一个或多个数字,只可以有一个E/e。
4)其他错误:范围错误,其他字符错误(非法字符,+号)
以下为解析数字可能通过的路径图:
4.String解析(lept_parse_string):
string语法是以一对双引号将字符括起来,即 ”字符 ”,所以遇到 ” 则进入string解析函数。
case '"': return lept_parse_string(c, v);
若字符串中本身含有双引号,则需要转义符反斜杠\ .如:”a,\”b”.
\”代表着字符串开始和结束,\(转义字符)表示进入转义字符解析
switch (ch) {
case '\"':
*len = c->top - head;
*str = lept_context_pop(c, *len);
c->json = p;
return LEPT_PARSE_OK;
case '\\':
switch (*p++) {
case '\"': PUTC(c, '\"'); break;
case '\\': PUTC(c, '\\'); break;
case '/': PUTC(c, '/' ); break;
case 'b': PUTC(c, '\b'); break;
case 'f': PUTC(c, '\f'); break;
case 'n': PUTC(c, '\n'); break;
case 'r': PUTC(c, '\r'); break;
case 't': PUTC(c, '\t'); break;
case 'u':
(略去case u的代码,基本文本解析可省去u这种情况)
JSON支持9种转义序列,其中第九种\u,即Unicode用来表示各种字符。Unicode的格式为:\uXXXX ,而它的转换格式UTF有很多种,其中UTF-8以字节为编码,所以不会存在字节序问题,且ASCII字符只占1字节,故最为流行。
而对于 JSON字符串中的 \uXXXX 是以 16 进制表示码点 U+0000 至 U+FFFF,我们需要以下步骤即可实现解析:
1)解析 4 位十六进制整数为码点;
2)由于字符串是以 UTF-8 存储,我们要把这个码点编码成 UTF-8。
(具体实现略)
我们在解析字符串这整个过程中由于处理的不是单独个体,所以需要借用栈来进行解析。我们要做的是备份栈顶 ---->将解析的字符压栈---->计算长度,一次弹出---->设置至值里
图1 备份栈顶,开始压栈
图2 不断压栈至结束
图3 将字符一次性弹出,分配内存,生成字符串值
5.Array解析(lept_parse_array):
一个数组中可以有一个或多个元素,可以为不同类型,用逗号分隔,所以数组类型属于复合数据类型(可嵌套),像[],[1,2,3],[[1,2],3]均合法。数组的语法为:
*array = %x5B ws [ value ( ws %x2C ws value ) ] ws %x5D
//%x5B 是左中括号 [,%x2C 是逗号 ,,%x5D 是右中括号 ] ,ws 是空白字符。
类似于解析JSON字符串,在解析数组时,因为在开始时不能知道数组的长度,而又需要进行转义,所以需要一个临时缓冲区去存储解析后的结果。我们为此实现了一个动态增长的堆栈,可以不断压入字符,最后一次性把整个字符串弹出,复制至新分配的内存之中。
解析过程类似于解析字符串,相当于是不断地压栈弹出的嵌套操作,将每一次弹出结果压栈,最后整个弹出,分配内存。图解只展示最后结果:
6.Object解析(lept_parse_object):
对象类似于数组,区别在于对象以花括号包裹,对象由对象成员,即键值对构成。键必须为 JSON 字符串,然后值是任何 JSON 值,中间以冒号 :(U+003A)分隔。对象语法为:
*member = string ws %x3A ws value
object = %x7B ws [ member ( ws %x2C ws member ) ] ws %x7D
解析对象步骤:
1)解析字符串
2)解析冒号
3)解析JSON任意值
4)解析逗号或右花括号
5)释放临时key及栈上的成员
//若以上四步出现错误导致解析失败,直接进行第五步后结束解析。
三、JSON生成
JSON解析是将JSON文本解析为树形数据结构,而生成器则是与其相反,通过生成器,我们将树形结构转换成单行、无空格的字符串。同时我们需要利用动态变长的堆栈做输出缓冲区来存储生成结果。