一文搞定json解析和封装问题,手把手带你学习CJSON开源代码
Posted CodeBowl
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文搞定json解析和封装问题,手把手带你学习CJSON开源代码相关的知识,希望对你有一定的参考价值。
cJSON保姆级教程
努力成为大佬!
JSON简介
JSON 全称 javascript Object Notation,即 JS对象简谱,是一种轻量级的数据格式。
它采用完全独立于编程语言的文本格式来存储和表示数据,语法简洁、层次结构清晰,易于人阅读和编写,同时也易于机器解析和生成,有效的提升了网络传输效率。
JSON对象是一个无序的"名称/值"键值对的集合:
- 以"
{
“开始,以”}
"结束,允许**「嵌套使用」**; - 每个**「名称和值成对出现」**,名称和值之间使用"
:
"分隔; - 键值对之间用"
,
"分隔 - 在这些字符前后允许存在无意义的空白符;
对于键值,可以有如下值:
-
一个新的**「json对象」**
-
「数组」:使用"
[
“和”]
"表示 -
「数字」:直接表示,可以是整数,也可以是浮点数
-
「字符串」:使用引号
"
表示 -
「字面值」:false、null、true中的一个(必须是小写)
示例:
-
{ "students" : [ { "name" : "XiaoMing", "age" : 10, "learning" : true }, { "name" : "XiaoHong", "age" : 11, "learning" : false } ] }
cJSON介绍
cJSON是一个使用C语言编写的JSON数据解析器,具有超轻便,可移植,单文件的特点,使用MIT开源协议。
cJSON项目托管在Github上,仓库地址如下:
https://github.com/DaveGamble/cJSON
可以看到里面的文件比较多,其中cJSON的源码文件只有两个:
-
cJSON.h
-
cJSON.c
使用的时候,只需要将这两个文件复制到工程目录,然后包含头文件
cJSON.h
即可,如下:#include "cJSON.h"
基本使用操作
关键数据结构
/* The cJSON structure: */
typedef struct cJSON
{
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
int valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
char *string;
} cJSON;
设计为一个结点结构体,有前后结点和孩子结点,type是该结点存放的数据类型,后面依次为数据存储。
设计思想
**根据上面结构体定义,设计的主要思想就是链表。**通过前后结点遍历,同时使用child*支持嵌套。
首先,它不是将一整段JSON数据抽象出来,而是将其中的一条JSON数据抽象出来,也就是一个键值对,用上面的结构体 strcut cJSON 来表示;
其次,一段完整的JSON数据中由很多键值对组成,并且涉及到键值对的查找、删除、添加,所以使用链表来存储整段JSON数据,如上面的代码所示:
next指针:指向下一个键值对
prev指针:指向上一个键值对
最后,因为JSON数据支持嵌套,所以一个键值对的值会是一个新的JSON数据对象(一条新的链表),也有可能是一个数组,方便起见,在cJSON中,数组也表示为一个数组对象,用链表存储,所以:
在键值对结构体中,当该键值对的值是一个嵌套的JSON数据或者一个数组时,由child指针指向该条新链表。
JSON数据解析
解析JSON数据的过程,其实就是剥离一个一个链表节点(键值对)的过程。
用于**「将字符串解析成json对象」**,若失败则返回NULL。
cJSON *cJSON_Parse(const char *value);
用于**「获取json对象中的某个节点」**,若失败,返回NULL,成功则返回该节点对象。
cJSON *cJSON_GetObjectItem(cJSON *object,const char *string);
用于释放json对象相关内存。
void cJSON_Delete(cJSON *c);
如果JSON数据的值是数组,可以通过下面接口获取JSON 数组
大小和数组里面的JSON 对象
int cJSON_GetArraySize(const cJSON *array);
cJSON * cJSON_GetArrayItem(const cJSON *array, int index);
解析步骤
从网上看到一个比较有趣的王者荣耀的json,下面以它为例:
[{
"ename": 105,
"cname": "廉颇",
"title": "正义爆轰",
"new_type": 0,
"hero_type": 3,
"skin_name": "正义爆轰|地狱岩魂"
}, {
"ename": 106,
"cname": "小乔",
"title": "恋之微风",
"new_type": 0,
"hero_type": 2,
"skin_name": "恋之微风|万圣前夜|天鹅之梦|纯白花嫁|缤纷独角兽"
}]
- 通过cJSON接口解析buffer中的字符串
- 获取JSON指定字段
解析示例:
#include <stdio.h>
#include "cJSON.h"
char *message =
"[ \\
{ \\
\\"ename\\": 105, \\
\\"cname\\" : \\"廉颇\\", \\
\\"title\\" : \\"正义爆轰\\", \\
\\"new_type\\" : 0, \\
\\"hero_type\\" : 3, \\
\\"skin_name\\" : \\"正义爆轰|地狱岩魂\\" \\
}, \\
{ \\
\\"ename\\": 106, \\
\\"cname\\" : \\"小乔\\", \\
\\"title\\" : \\"恋之微风\\", \\
\\"new_type\\" : 0, \\
\\"hero_type\\" : 2, \\
\\"skin_name\\" : \\"恋之微风|万圣前夜|天鹅之梦|纯白花嫁|缤纷独角兽\\" \\
} \\
]";
int main(void)
{
cJSON *pJson = NULL;
cJSON *pVal = NULL;
cJSON *pTemp = NULL;
/*创建cJSON对象*/
pJson = cJSON_Parse(message);
if (NULL == pJson)
{
printf("parse json failed\\n");
return -1;
}
/*获取cJSON数组数量*/
int num = cJSON_GetArraySize(pJson);
/*遍历每一个cJSON数组元素*/
for (int index = 0; index<num; index++)
{
/*获取cJSON数组中的第index个cJSON对象*/
pTemp = cJSON_GetArrayItem(pJson, index);
/*获取cJSON对象中的key值为ename的对象*/
pVal = cJSON_GetObjectItem(pTemp, "ename");
printf("ename:%d\\n", pVal->valueint);
pVal = cJSON_GetObjectItem(pTemp, "cname");
printf("cname:%s\\n", pVal->valuestring);
pVal = cJSON_GetObjectItem(pTemp, "title");
printf("title:%s\\n", pVal->valuestring);
pVal = cJSON_GetObjectItem(pTemp, "new_type");
printf("new_type:%d\\n", pVal->valueint);
pVal = cJSON_GetObjectItem(pTemp, "hero_type");
printf("hero_type:%d\\n", pVal->valueint);
pVal = cJSON_GetObjectItem(pTemp, "skin_name");
printf("skin_name:%s\\n\\n", pVal->valuestring);
printf("====================================\\n\\n");
}
/*释放内存*/
cJSON_Delete(pJson);
pJson = NULL;
return 0;
}
本示例,演示了解析数组,还有解析单项的操作,其他解析嵌套等操作,大家可以自由探索。
在本示例中,因为我提前知道数据的类型,比如字符型或者浮点型,所以我直接使用指针指向对应的数据域提取,在实际使用时,如果提前不确定数据类型,应该先判断type的值,确定数据类型,再从对应的数据域中提取数据。
json数据封装
封装JSON数据的过程,其实就是**「创建链表」和「向链表中添加节点」**的过程。
首先来讲述一下链表中的一些术语:
- 「头指针」:指向链表头结点的指针;
- 「头结点」:不存放有效数据,方便链表操作;
- 「首节点」:第一个存放有效数据的节点;
- 「尾节点」:最后一个存放有效数据的节点;
封装步骤
明白了这几个概念之后,我们开始讲述**「创建一段完整的JSON数据」,即如何「创建一条完整的链表」**。
- ① 「创建头指针」:
cJSON* cjson_test = NULL;
- ② 「创建头结点」,并将头指针指向头结点:
cjson_test = cJSON_CreateObject();
- ③ 尽情的向链表中**「添加节点」**:
/* 添加一个值为 null 的布尔类型的JSON数据(添加一个链表节点) */
cJSON_AddNullToObject(cJSON * const object, const char * const name);
/* 添加一个值为 true 的布尔类型的JSON数据(添加一个链表节点) */
cJSON_AddTrueToObject(cJSON * const object, const char * const name);
/* 添加一个值为 False 的布尔类型的JSON数据(添加一个链表节点) */
cJSON_AddFalseToObject(cJSON * const object, const char * const name);
/* 添加一个值为布尔类型的JSON数据 0:false 非0:true (添加一个链表节点) */
cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
/* 添加一条数值类型的JSON数据(添加一个链表节点) */
cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
/* 添加一条字符串类型的JSON数据(添加一个链表节点) */
cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
/* 添加一行数据(添加一个链表节点) */
cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
/* 添加一个空对象(添加一个链表节点) */
cJSON_AddObjectToObject(cJSON * const object, const char * const name);
/* 添加一个空数组(添加一个链表节点) */
cJSON_AddArrayToObject(cJSON * const object, const char * const name);
/* 添加一个嵌套的JSON对象/数组(添加一个链表节点) */
cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
cJSON还提供了将JSON对象转换成字符串输出到终端
char *cJSON_Print(const cJSON *item);
封装示例
#include <stdio.h>
#include "cJSON.h"
int main(void)
{
cJSON* cjson_test = NULL;
cJSON* cjson_address = NULL;
cJSON* cjson_skill = NULL;
char* str = NULL;
/* 创建一个JSON数据对象(链表头结点) */
cjson_test = cJSON_CreateObject();
/* 添加一个值为 null 的布尔类型的JSON数据(添加一个链表节点) */
cJSON_AddNullToObject(cjson_test, "null_test");
/* 添加一个值为 true 的布尔类型的JSON数据(添加一个链表节点) */
cJSON_AddTrueToObject(cjson_test,"true_test");
/* 添加一个值为 False 的布尔类型的JSON数据(添加一个链表节点) */
cJSON_AddFalseToObject(cjson_test, "false_test");
/* 添加一个值为布尔类型的JSON数据 0:false 非0:true (添加一个链表节点) */
cJSON_AddBoolToObject(cjson_test, "bool_test", 0);
/* 添加一条整数类型的JSON数据(添加一个链表节点) */
cJSON_AddNumberToObject(cjson_test, "int_test", 22);
/* 添加一条浮点类型的JSON数据(添加一个链表节点) */
cJSON_AddNumberToObject(cjson_test, "double_test", 55.5);
/* 添加一条字符串类型的JSON数据(添加一个链表节点) */
cJSON_AddStringToObject(cjson_test, "str_test", "我是字符串");
/* 添加一行任意数据(添加一个链表节点) */
cJSON_AddRawToObject(cjson_test, "key", "任意数据");
/* 添加一个空对象(添加一个链表节点) */
cJSON_AddObjectToObject(cjson_test, "objet");
/* 添加一个嵌套的JSON对象(添加一个链表节点) */
cjson_address = cJSON_CreateObject();
cJSON_AddStringToObject(cjson_address, "country", "China");
cJSON_AddNumberToObject(cjson_address, "zip-code", 111111);
cJSON_AddItemToObject(cjson_test, "address", cjson_address);
/* 添加一个空数组(添加一个链表节点) */
cJSON_AddArrayToObject(cjson_test, "Array");
/* 添加一个数组类型的JSON数据(添加一个链表节点) */
cjson_skill = cJSON_CreateArray();
cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "C" ));
cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "C++" ));
cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "Python" ));
cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "Java"));
cJSON_AddItemToObject(cjson_test, "skill", cjson_skill);
/* 打印JSON对象(整条链表)的所有数据 */
str = cJSON_Print(cjson_test);
printf("%s\\n", str);
return 0;
}
使用注意事项
内存及时释放
cJSON的所有操作都是基于链表的,所以cJSON在使用过程中大量的使用malloc从堆中分配动态内存的,所以在使用完之后,应当及时调用下面的函数,清空cJSON指针所指向的内存,该函数也可用于删除某一条数据:
(void) cJSON_Delete(cJSON *item);
注意:该函数删除一条JSON数据时,如果有嵌套,会连带删除。
内存钩子
cJSON在支持自定义malloc函数和free函数,方法如下:
① 使用cJSON_Hooks来连接自定义malloc函数和free函数:
typedef struct cJSON_Hooks
{
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
void *(CJSON_CDECL *malloc_fn)(size_t sz);
void (CJSON_CDECL *free_fn)(void *ptr);
} cJSON_Hooks;
② 初始化钩子cJSON_Hooks
(void) cJSON_InitHooks(cJSON_Hooks* hooks);
阅读心得
该项目是纯c编写的,运用了大量的指针和动态申请内存,以及一些语言特性static、const的运用,作为C或C++的学习项目还挺有用的。
下面是总结:
比较巧妙的地方
1.链表的使用
2.hooks的使用
typedef struct cJSON_Hooks
{
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
void *(CJSON_CDECL *malloc_fn)(size_t sz);
void (CJSON_CDECL *free_fn)(void *ptr);
} cJSON_Hooks;
typedef struct internal_hooks
{
void *(CJSON_CDECL *allocate)(size_t size);
void (CJSON_CDECL *deallocate)(void *pointer);
void *(CJSON_CDECL *reallocate)(void *pointer, size_t size);
} internal_hooks;
json的设计格式
如何用c编写json格式的语言,我找到了作者设计时候的参考图,大家可以简单了解一下:
object是名称/值 的无序集合:
数组的设计:
键值的设计:
string是一个零个或多个Unicode字符的序列,用双引号包装,使用反斜杠转义。字符表示为单个字符串。字符串非常类似于C或Java字符串。
数字 非常类似于C或Java数字,只是不使用八进制和十六进制格式。
空格可以插入到任意对令牌之间。除了一些编码细节外,这完全描述了语言。
可以看到都是用空格做间隔的。
char *message =
"[ \\
{ \\
\\"ename\\": 105, \\
\\"cname\\" : \\"廉颇\\", \\
\\"title\\" : \\"正义爆轰\\", \\
\\"new_type\\" : 0, \\
\\"hero_type\\" : 3, \\
\\"skin_name\\" : \\"正义爆轰|地狱岩魂\\" \\
}, \\
{ \\
\\"ename\\": 106, \\
\\"cname\\" : \\"小乔\\", \\
\\"title\\" : \\"恋之微风\\", \\
\\"new_type\\" : 0, \\
\\"hero_type\\" : 2, \\
\\"skin_name\\" : \\"恋之微风|万圣前夜|天鹅之梦|纯白花嫁|缤纷独角兽\\" \\
} \\
]";
参考资料
以上是关于一文搞定json解析和封装问题,手把手带你学习CJSON开源代码的主要内容,如果未能解决你的问题,请参考以下文章
专知国庆特刊-PyTorch手把手深度学习教程系列01一文带你入门优雅的PyTorch