likes
comments
collection
share

cJson之parse_value、parse_object和parse_array(五)

作者站长头像
站长
· 阅读数 46

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个字符是否相等,区分大小写 cJson之parse_value、parse_object和parse_array(五)
  • 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_StringvalueString=“hello”
cJSON_NULL
cJSON_Numbervaluedouble=1.5
cJSON_False
cJSON_Truevalueint=1
cJSON_Array可以有
cJSON_Object可以有

parse_object

parse_object是在前面的parse_number、parse_string、parse_value的基础上,重点解决数据关系的建立。先看一个简单的例子

{
    "one": 1,
    "two": 2,
    "three": 3
}

parse_object是怎么解析的呢?

  1. 首先再次确定是'{'开头的
 if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{'))
{
    goto fail; /* not an object */
}
  1. 然后是新建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;
}
  1. 键值对中间是冒号':',通过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 */
}
  1. 最后链表头节点指向尾节点,并建立cJSON_Object和链表头节点的关系,为什么要把头节点指向尾节点呢?这是为了以后添加新节点的时候,可以立马定位到尾节点,而不用遍历到尾节点
 if (head != NULL) {
        head->prev = current_item;
    }

    item->type = cJSON_Object;
    item->child = head;

结果如下图所示 cJson之parse_value、parse_object和parse_array(五)

上面的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_Arrayvalue
cJSON_Objectkey-value

代码和parse_object类似,没有parse_string和对分号的判断,只需要parse_value,下面是一个数组的数据

[
    1,
    null,
    true,
    false,
    [],
    "hello",
    {}
]

解析后的数据结构如下图 cJson之parse_value、parse_object和parse_array(五)

除了正常的数据,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
评论
请登录