cJson之parse_value、parse_object和parse_array(五)

cJson之环境搭建(一) 快速搭建cJson项目环境

cJson之parse_number(二) 数字解析

cJson之parse_hex4(三) 4位16进制字符解析

cJson之parse_string(四) 字符串解析

Json表达的内核是什么?其实就是键值对。

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类型
rust 复制代码
/* 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;
}
  • 布尔类型,判断同上
rust 复制代码
/* 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;
}
  • 字符串类型,首字符是引号\"本身,字符串解析在第四章分析过
scss 复制代码
/* string */
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"'))
{
    return parse_string(item, input_buffer);
}
  • 数字类型,首字符是正负号,或者0-9的数字,数字解析在第二章分析过
scss 复制代码
/* 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一节
kotlin 复制代码
/* array */
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '['))
{
    return parse_array(item, input_buffer);
}
  • 对象类型,首字符是大括号,具体内容看下面parse_object一节
scss 复制代码
/* object */
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{'))
{
    return parse_object(item, input_buffer);
}

parse_value是解析Json的入口,它在分析完数据类型后,就交给具体的方法去解析,重点在于判断数据类型,下面是一些测试用例

scss 复制代码
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);

涵盖了常见的数据类型

arduino 复制代码
#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

c 复制代码
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的基础上,重点解决数据关系的建立。先看一个简单的例子

json 复制代码
{
    "one": 1,
    "two": 2,
    "three": 3
}

parse_object是怎么解析的呢?

  1. 首先再次确定是'{'开头的
less 复制代码
 if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{'))
{
    goto fail; /* not an object */
}
  1. 然后是新建cJson,使用链表串起来
ini 复制代码
 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两步
less 复制代码
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和链表头节点的关系,为什么要把头节点指向尾节点呢?这是为了以后添加新节点的时候,可以立马定位到尾节点,而不用遍历到尾节点
ini 复制代码
 if (head != NULL) {
        head->prev = current_item;
    }

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

结果如下图所示

上面的value比较单一,都是整数,parse_object.c文件中有全类型的测试用例,解析后的结构和上面是一样的

json 复制代码
{
    "one": 1,
    "NULL": null,
    "TRUE": true,
    "FALSE": false,
    "array": [],
    "world": "hello",
    "object": {}
}

除了正常的数据外,还有一些异常测试用例

scss 复制代码
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,下面是一个数组的数据

csharp 复制代码
[
    1,
    null,
    true,
    false,
    [],
    "hello",
    {}
]

解析后的数据结构如下图

除了正常的数据,parse_array.c文件中还有一些异常用例,异常场景用来验证代码是否可以正常检测出来并处理

vbscript 复制代码
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数据也不过是简单的重复

相关推荐
Vic1010122 分钟前
Hutool 的完整 JSON 工具类示例
开发语言·json
电商数据girl7 小时前
如何利用API接口与网页爬虫协同进行电商平台商品数据采集?
大数据·开发语言·人工智能·python·django·json
拷斤锟13 小时前
使用Excel解析从OData API获取到的JSON数据
数据库·json·excel
有育哥无奔波2 天前
是采用示例模板,还是采用json的结构化数据,哪种方式会让llm的输出更加稳定?
json
小小李程序员2 天前
JSON.parse解析大整数踩坑
开发语言·javascript·json
西哥写代码3 天前
基于dcmtk的dicom工具 第九章 以json文件或sqlite为数据源的worklist服务(附工程源码)
sqlite·json·mfc·dcmtk·worklist
南玖yy3 天前
C++多态:面向对象编程的灵魂之
运维·开发语言·数据库·c++·后端·c·c语音
Mu.3873 天前
JSON解析
json
Monkey-旭3 天前
Android JNI 语法全解析:从基础到实战
android·java·c++·c·jni·native
我今晚不熬夜4 天前
JSON在java中的使用
java·开发语言·json