cJson之parse_value、parse_object和parse_array(五)
Json表达的内核是什么?其实就是键值对。
{
"name":"华为mate 60 pro",
"price":6999,
"5G":true,
"size":6.69,
"功能":[
"卫星通话",
"全向变焦",
"无线充电"
],
"联系方式":{
"qq":"12345678",
"tel":12345678
}
}
键的类型相对简单,就是字符串,上一节parse_string主要解决的就是键的解析;而值的类型相对复杂,有字符串、整数、浮点数、布尔值、数组、对象。
parse_value就是要处理这些数据类型的解析,那是如何解析的呢?
parse_value
我们将parse_value函数分成功能段来看,其中多次使用了can_read、buffer_at_offset和strncmp方法,先说明一下
- can_read是判断当前offset+size是否超过字符串总长度,防止溢出;
- buffer_at_offset就是从content开始偏移offset后指向的位置,如下图就是字符t所在位置指针
- strncmp 库函数,比较两个字符串的前n个字符是否相等,区分大小写
- null类型,如果当前位置的4个字符是"null",那就是null类型
/* null */
if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0))
{
item->type = cJSON_NULL;
input_buffer->offset += 4;
return true;
}
- 布尔类型,判断同上
/* false */
if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0))
{
item->type = cJSON_False;
input_buffer->offset += 5;
return true;
}
/* true */
if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0))
{
item->type = cJSON_True;
item->valueint = 1;
input_buffer->offset += 4;
return true;
}
- 字符串类型,首字符是引号\"本身,字符串解析在第四章分析过
/* string */
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"'))
{
return parse_string(item, input_buffer);
}
- 数字类型,首字符是正负号,或者0-9的数字,数字解析在第二章分析过
/* number */
if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9'))))
{
return parse_number(item, input_buffer);
}
- 数组类型,首字符是中括号,具体内容看下面parse_array一节
/* array */
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '['))
{
return parse_array(item, input_buffer);
}
- 对象类型,首字符是大括号,具体内容看下面parse_object一节
/* object */
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{'))
{
return parse_object(item, input_buffer);
}
parse_value是解析Json的入口,它在分析完数据类型后,就交给具体的方法去解析,重点在于判断数据类型,下面是一些测试用例
assert_parse_value("null", cJSON_NULL);
assert_parse_value("true", cJSON_True);
assert_parse_value("false", cJSON_False);
assert_parse_value("1.5", cJSON_Number);
assert_parse_value("\"\"", cJSON_String);
assert_parse_value("\"hello\"", cJSON_String);
assert_parse_value("[]", cJSON_Array);
assert_parse_value("{}", cJSON_Object);
涵盖了常见的数据类型
#define cJSON_False (1 << 0)
#define cJSON_True (1 << 1)
#define cJSON_NULL (1 << 2)
#define cJSON_Number (1 << 3)
#define cJSON_String (1 << 4)
#define cJSON_Array (1 << 5)
#define cJSON_Object (1 << 6)
解析出来的数据怎么表达呢?使用的就是第二节讲过的核心数据结构cJSON
typedef struct cJSON
{
// array、object类型的cJson可能有前后节点
struct cJSON *next;
struct cJSON *prev;
// array、object类型的cJson可能有子节点
struct cJSON *child;
// 这个cJson的类型,数字、字符串、布尔值、数组等
int type;
// 当类型是cJSON_String 或 cJSON_Raw 时的字符串值
char *valuestring;
// int类型值 最好使用cJSON_SetNumberValue设置 */
int valueint;
//当类型是cJSON_Number的浮点值,也同时会给valueint赋值 */
double valuedouble;
// Json对象中子项的名字,
char *string;
} cJSON;
当然,parse_value的测试用例比较简单,就上面的一些用例,cJson结构体只需要类型和值两个字段就表示了解析的数据,还没能体现cJson结构体的表达能力,完整的还得看parse_object
类型 | 值 | 子节点 |
---|---|---|
cJSON_String | valueString=“hello” | 无 |
cJSON_NULL | 无 | 无 |
cJSON_Number | valuedouble=1.5 | 无 |
cJSON_False | 无 | 无 |
cJSON_True | valueint=1 | 无 |
cJSON_Array | 无 | 可以有 |
cJSON_Object | 无 | 可以有 |
parse_object
parse_object是在前面的parse_number、parse_string、parse_value的基础上,重点解决数据关系的建立。先看一个简单的例子
{
"one": 1,
"two": 2,
"three": 3
}
parse_object是怎么解析的呢?
- 首先再次确定是'{'开头的
if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{'))
{
goto fail; /* not an object */
}
- 然后是新建cJson,使用链表串起来
cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks));
...
/* 还没有头,建立表头 */
if (head == NULL)
{
current_item = head = new_item;
}
else
{
/* 添加到表尾,建立前后关系 */
current_item->next = new_item;
new_item->prev = current_item;
current_item = new_item;
}
- 键值对中间是冒号':',通过parse_string解析键,通过parse_value解析值,把解析的数据存在current_item中。键值对之间是逗号分隔,parse_value结束后会判断是否还有逗号,如果有的话还需要重复2、3两步
if (!parse_string(current_item, input_buffer))
{
goto fail; /* failed to parse name */
}
...
if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':'))
{
goto fail; /* invalid object */
}
...
if (!parse_value(current_item, input_buffer))
{
goto fail; /* failed to parse value */
}
- 最后链表头节点指向尾节点,并建立cJSON_Object和链表头节点的关系,为什么要把头节点指向尾节点呢?这是为了以后添加新节点的时候,可以立马定位到尾节点,而不用遍历到尾节点
if (head != NULL) {
head->prev = current_item;
}
item->type = cJSON_Object;
item->child = head;
结果如下图所示
上面的value比较单一,都是整数,parse_object.c文件中有全类型的测试用例,解析后的结构和上面是一样的
{
"one": 1,
"NULL": null,
"TRUE": true,
"FALSE": false,
"array": [],
"world": "hello",
"object": {}
}
除了正常的数据外,还有一些异常测试用例
assert_not_object("");
assert_not_object("{");
assert_not_object("}");
assert_not_object("[\"hello\",{}]");
assert_not_object("42");
assert_not_object("3.14");
assert_not_object("\"{}hello world!\n\"");
parse_array
掌握了对象的解析,数组的解析可谓信手拈来,它们之间非常类似,主要区别如下
- 对象的子节点是键值对
- 数组的子节点只有值,
类型 | child |
---|---|
cJSON_Array | value |
cJSON_Object | key-value |
代码和parse_object类似,没有parse_string和对分号的判断,只需要parse_value,下面是一个数组的数据
[
1,
null,
true,
false,
[],
"hello",
{}
]
解析后的数据结构如下图
除了正常的数据,parse_array.c文件中还有一些异常用例,异常场景用来验证代码是否可以正常检测出来并处理
assert_not_array("");
assert_not_array("[");
assert_not_array("]");
assert_not_array("{\"hello\":[]}");
assert_not_array("42");
assert_not_array("3.14");
assert_not_array("\"[]hello world!\n\"");
结尾
至此,Json的解析方法就介绍结束了,对于复杂的Json数据也不过是简单的重复
转载自:https://juejin.cn/post/7278305453598720061