【万字详解】cJSON解析

目录

1、通过README文件,初步了解cJSON:

1.1、头文件的开头和结尾:

1.2、头文件关于cJSON类型的宏定义

1.3、头文件中的extern

2、阅读并且分析cJSON源码

2.1、结构体struct cJSON(算法设计思想):

2.2、结构体stuct cJSON_Hooks:

2.3、cJSON_InitHooks函数

2.4、cJSON_New_Item函数

2.5、cJSON_Delete函数

2.6、parse_number函数

2.6、pow2gt函数

2.8、update函数

2.9、print_number函数

2.1.1、parse_hex4函数

2.1.2、parse_string函数

2.1.3、print_string_ptr函数

2.1.4、cJSON_ParseWithOpts函数

2.1.5、parse_array函数

2.1.6、parse_object函数

2.1.7、print_object函数

2.1.8、print_array函数

3、cJSON实例

3.1、源码包中test.c测试代码

3.2、用例


1、通过README文件,初步了解cJSON:

从README文件内容的介绍来看,cJSON是一个用C语言编写的JSON数据解析器,它轻便,可移植,是一个简单易用的解析器。它cJson的源码文件只有两个:一个C 文件(cJSON.c)和一个头文件(cJSON.h)。因为它的简单易用,让其GitHub吸引了全球众多的程序员在它的基础上贡献代码。可以使用git clone https://github.com/DaveGamble/cJSON.git语句拷贝到本地,也可以到cJSON download | SourceForge.net网址上下载到本地。

1.1、头文件的开头和结尾:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16  

#ifndef cJSON_h,#define cJSON_h,#endif . 这是为了防止头文件被重复引用。

extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般之包括函数名。

1.2、头文件关于cJSON类型的宏定义

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

这些宏定义是对结构体type的值定义,处理时只需要将type的值&255进行位运算,即可得到json里储存的数据类型。

1.3、头文件中的extern

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

extern是C语言中的一个关键字,一般用在变量名前或函数名前,作用是用来说明“此变量/函数是在别处定义的,要在此处引用”

2、阅读并且分析cJSON源码

2.1、结构体struct cJSON(算法设计思想):

在头文件(cJSON.h)中:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

cJSON用了一个结构体来表示一个JSON的数据,它并不是把一整段的JSON数据全部抽象出来,而是把其中的一条JSON数据抽象出来。

cjson的存储结构像一个广义表,其实也可以说是一个树,不过兄弟结点之间都通过prev和next两个指针连接起来。

prev和next分别是cjson对象的前驱和后继,属于同一级别的对象。chid则指向孩子结点,并且是第一个孩子的指针。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

分析:

  1. Next指针:用于指向下一个键值对

Prev指针:用于指向上一个键值对

JSON是一段数据,结构体对其中一条数据进行存储,为了能对整段数据进行遍历、查找、删除、添加,cJSON用了链表进行存储。

2、Child指针:用于指向新的链表

3、String:表示该条JSON的数据名称;

type:表示该条JSON的数据类型;

valuestring:如果type是字符串类型,则将该指针指向该条数据,表示数据的值;

valueint:如果type是整数,则将该指针指向该条数据,表示数据的值;

valuedouble:如果type是浮点数,则将该指针指向该条数据,表示数据的值;

结构体中定义了一系列的成员变量来存放值,以一种键值对的形式来存储,简洁优美。

2.2、结构体stuct cJSON_Hooks:

在头文件(cJSON.h)中:

833d7584a5384d8cb307ecf4c3ccdfac.png

分析:定义了malloc函数以及free函数,malloc 函数其实就是在内存中:找一片指定大小的空间,然后将这个空间的首地址给一个指针变量,这里的指针变量可以是一个单独的指针,也可以是一个数组的首地址, 这要看malloc函数中参数size的具体内容。Free函数是将之前用malloc分配的空间还给程序或者是操作系统,也就是释放了这块内存,让它重新得到自由。通过结构体 struct cJSON_Hooks跟内部调用的内存分配挂钩。

2.3、cJSON_InitHooks函数

在C文件(cJSON.c)中:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

在cJSON项目里,留有cJSON_InitHooks()函数,他是Hooks内存管理函数。

分析:

  1. if语句中表示,如果hooks为空,那么将使用默认的内存管理,然后返回。
  2. 也可以自定义内存管理函数,这里用到了三目运算符,与函数指针结合。如果自定义了

malloc函数和free函数,则指针指向自定义的函数,否则指向默认函数。

2.4、cJSON_New_Item函数

在C文件(cJSON.c)中:

cc444b9c1bff4879a1baedab9835369f.png

它不仅能初始化内存,还返回了一个cJSON节点。

分析:

  1. 定义一个node作为返回值,node指向一个新的cJSON节点。malloc出一个节点, malloc函数能分配空间给它。
  2. If语句,如果成功malloc出节点,则调用memset函数,将内存初始化为0,memset函数是标准库<string.h>中的函数。
  3. 返回node,返回类型是(cJSON*)型。

2.5、cJSON_Delete函数

在C文件(cJSON.c)中:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

它可以用来删除一整个的JSON数据,同时将所有的节点全部释放内存。

分析:

  1. 传入一整个cJSON类型数据,指向指针c。
  2. 定义一个cJSON类的next指针,用来递归。
  3. 如果当前的指针c指向的cJSON节点不为空,进入循环体进行删除操作。
  4. 先定义next保存当前指针c的下一个指针指向位置,用来后面的递归遍历c=next
  5. 如果传入的是cJSON结构,并且c->child,则调用cJSON_Delete函数删除嵌套的孩子链表。
  6. 如果传入的是cJSON结构,并且c->valuestring,则调用cJSON_free函数释放该节点内存。
  7. 如果传入的是cJSON结构,并且c->string,则调用cJSON_free函数释放该节点内存。

2.6、parse_number函数

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

Parse_number函数能把数字字符数组转换为基本数据类型赋值给一条cJSON数据,并且返回数字字符数组。

分析:

  1. 第一个if,判断正负。
  2. 第二个if判断当前num指向是否为0
  3. 第三个if表示如果当前num指向1~9,则将字符解析为int类型(*num++-‘0’),同时,num指针自增指向下一个字符。通过这个循环体,用来将字符串解析为int类型,并且保存在n中。
  4. 第四个if表示,如果当前num指向“.”,即存在小数部分,并且,小数的下一位字符是合法数字字符,就对小数部分进行求解,依旧是循环体,用来将字符串解析为int类型,并且保存在n中。同时,scale自减,表示小数点的位数。Scale=-1,表示小数点后有1位。
  5. 第五个if表示,如果当前num指向“e”或者“E”,即存在指数,就对指数部分求解,先判断当前num自增指向的下一个字符,if下一个字符是“+”则继续自增,else if下一个字符是“-”,则设置signsubscale为-1,表示指数为负数,继续自增。如果下一个指向的是合法数字字符,则进入循环体,将字符串解析为int类型,并且保存在subscale中。
  6. sign代表数据的正负,n表示数据整数位和小数位,调用math中的pow函数,scale表示小数位数,subscale表示指数位,signsubscale表示指数位数正负。将最终的结果保存在n中
  7. 将n赋值给cJSON数据中的valuedouble和valueint,设置类型为cJSON_Number

2.6、pow2gt函数

fb923f6ac22d4576abc8702d94594056.png

该函数作用是返回比x大的最小的2的N次方数。

分析:

  1. int类型在c语言中占4个字节,32位
  2. 举个例子:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_17,color_FFFFFF,t_70,g_se,x_16

  1. 对于任何一个M,当它执行完上图中的5步操作后,M的最左边为1的那位向右全部被赋值为1,因此,在最后return x+1 时,就会返回>=M最小的2的N次方数。而最开始的–x,是因为当x本身就是一个2的N次方数时,返回值为x本身,所以需要减1来保证正确性。

2.7、结构体struct printbuffer与ensure函数

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

结构体printbuffer内定义了一系列的与内存有关的变量,ensure函数用来分配空间,确保内存够用,并且返回可用的内存指针。每次给printbuffer赋值前,都需要计算所需的内存,并调用ensure()确保内存够用。

分析:

  1. 如果当前的printbuffer指针指向空或者内部变量buffer为空,则返回0,表示无法分配内存。
  2. needed表示需要的内存大小,初始化,needed加上偏移量offset
  3. if所需的内存足够用,则返回可用的内存的起点
  4. 如果不够用,则调用pow2gt函数,分配新的内存大小,为了合理分配,设置一个newsize保存大于所需内存的最小2的N次方数
  5. 按照newsize的大小,调用cJSON_malloc重新开辟一个新的空间。
  6. 如果无法开辟新的空间,则调用cJSON_free释放printbuffer内存,并且返回0,表示无法分配内存。
  7. 如果成功开辟新的空间,则调用标准库<string.h>中的memcpy()函数,从存储区buffer复制length个字节到存储区新的空间(newbuffer)中。
  8. 然后再释放buffer的内存。
  9. 给p装上新的buffer和length

10、最后返回新分配的可用内存的起点

2.8、update函数

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

更新函数,用来更新printbuffer中的偏移量offset,返回更新后的offset

分析:

  1. 定义指针str
  2. 如果当前p指针为空或者p指针内部变量buffer为空,则返回0
  3. 否则,str指针指向buffer+offset这个内存地址的起点
  4. 最后返回更新之后的偏移量offset。

2.9、print_number函数

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

打印数字函数

分析:

  1. 定义一个字符串,作为返回值。
  2. 定义一个浮点型d,保存传入的cJSON数据中的valuedouble值。
  3. 第一个if:如果d为0,且p不为空,则确保p所需要的内存足够,如果p不为空,则申请一个大小为2字节的新的空间。如果str不为空,则调用标准库 - <string.h>中的strcpy函数,把“0”复制到str中。
  4. 第一个else  if:DBL_EPSILON表示是最小的正数INT_MAX, INT_MIN表示是最大的int值和最小的int值。这里相当于用来判断item里面存储的是不是一个int型的正整数。

如果是,且p不为空,则确保p所需要的内存足够,如果p不为空,则申请一个大小为21字节的新的空间。如果str不为空,则调用sprintf函数,把格式化的数据写入字符串str。所以这里的意思是,把item->valueint数字写入str字符串。

  1. 后一个else语句:相当于用来判断item里面存储的是不是一个浮点型。如果是,且p不为空,则确保p所需要的内存足够。如果p不为空,则申请一个大小为64的新的空间。如果str不为空,则按照不同精度,调用sprintf函数,把格式化的数据写入字符串str。floor函数表示向下取整,%.0f指不指定数据的长度,小数点后保留0位,也就是不保留小数部分。%e表示用科学计数法输出,%f指输出一个float型的值。

2.1.1、parse_hex4函数

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

该函数能把四位16进制的数转为十进制的数。

2.1.2、parse_string函数

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

该函数能从字符串中解析字符串,解析的时候会把 转换成真正的回车和换行,而不是简单的字符串复制。

2.1.3、print_string_ptr函数

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

分析一下未满足条件的,也就是flag为1的时候。这就是,在上图的黄色框中的部分的条件成立的,即可以字符串的第一个字符是双引号或反斜杠或者ASCII码小于32的,我们先看看小于32的ASCII码包括哪些内容,如下图:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

可以看到,都是一些控制字符串,非显示字符串,控制字符是用来实现特定操作的,如一些比较常用的控制字符就是ESC、BACKSPACE(删除上一个字符)、Del(删除当前字符)、回车符、换行符等。首先判断是不是空串,如果是就直接输出俩双引号。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

接下来,我们来分析最后的while()部分。这部分其实也很好理解,就是是显示字符就直接保存,不是的话需要判断以及加一些反斜杠等。需要注意的是最后default语句,sprintf()函数和printf()函数是相似的,前面加个  s  表示的是string嘛,不同的是printf()是将内容写在窗口上,而sprintf()函数是将内容写到其第一个参数里面,在这里指的是ptr2.而“ux”指的是以4位16进制写入,0表示的是不足4为就补0,4表示的是4位的意思,x表示的是16进制。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

Print_string函数内部调用了print_String_ptr,能针对一条具体的cJSON数据进行打印

2424908b19bc455492c32e4e548633b7.png

2.1.4、cJSON_ParseWithOpts函数

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

ParseWithOpts允许JSON是空终止的,并检索到解析的最后一个字节的指针。如果在return_parse_end中提供一个ptr,但解析失败,那么return_parse_end将包含一个指向    错误的指针,因此将匹配cJSON_GetErrorPtr()。cJSON_parse函数内部调用了cJSON_ParseWithOpts函数。

2.1.5、parse_array函数

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

该函数能将字符串解析为数组。解析数组时,其基本思想是对数组中每一个元素递归调用parse_value,再将这些元素连接形成一个链表。

2.1.6、parse_object函数

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

该函数可以将字符串解析为对象。

2.1.7、print_object函数

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

该函数能用来打印JSON对象。

2.1.8、print_array函数

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

分析else部分,大抵意思应该挺明显的,就是把链表中的每个结点的里的元素存储在entries中。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

按逻辑分析,下面这个也很好理解了,就是将所有的字符串串成一个整的字符串,包括中括号,逗号等符号。其实这样也就分析完了。在细枝末节的东西就不扣了,也都是一些比较基本的东西,包括出错的处理了等等。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

该函数能用来打印数组

3、cJSON实例

3.1、源码包中test.c测试代码

部分运行结果:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

3.2、用例

实例:

{

    "name": "Andy",      //键值对1

    "age": 20              //键值对2

}

代码展示:

void Parse_Str1(void){    char str1[] = "{"name":"Andy","age":20}";    cJSON *str1_json, *str1_name, *str1_age;    printf("str1:%s

",str1);    str1_json = cJSON_Parse(str1);   //创建JSON解析对象,返回JSON格式是否正确    if (!str1_json)    {        printf("JSON格式错误:%s

", cJSON_GetErrorPtr()); //输出json格式错误信息    }    else    {        printf("JSON格式正确:
%s

",cJSON_Print(str1_json) );        str1_name = cJSON_GetObjectItem(str1_json, "name"); //获取name键对应的值的信息        if (str1_name->type == cJSON_String)        {            printf("姓名:%s
", str1_name->valuestring);        }        str1_age = cJSON_GetObjectItem(str1_json, "age");   //获取age键对应的值的信息        if(str1_age->type==cJSON_Number)        {            printf("年龄:%d
", str1_age->valueint);        }        cJSON_Delete(str1_json);//释放内存    }}

运行结果:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bGx5pyq5a2k44GG,size_20,color_FFFFFF,t_70,g_se,x_16

参考:https://blog.csdn.net/qq_38289815/article/details/103307262

  • 45
    点赞
  • 284
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值