LV01-21-C语言-JSON-02-cJSON

本文主要是C语言基础——cJSON简介与使用的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
PC端开发环境 Windows Windows11
Ubuntu Ubuntu20.04.2的64位版本
VMware® Workstation 17 Pro 17.6.0 build-24238078
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Win32DiskImager Win32DiskImager v1.0
Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
点击查看本文参考资料
参考方向 参考原文
------
点击查看相关文件下载
--- ---

一、cJSON简介

1. cJSON是什么?

cJSON是一个使用C语言编写的JSON数据解析器,具有超轻便,可移植,单文件的特点,使用MIT开源协议。项目托管在Github上,仓库地址在这里:GitHub - DaveGamble/cJSON: Ultralightweight JSON parser in ANSI C

2. 源码说明

我们可以用以下命令拉取cJSON源码:

1
git clone https://github.com/DaveGamble/cJSON.git

从Github拉取cJSON源码后,文件非常多,但是其中cJSON的源码文件只有两个:cJSON.hcJSON.c。使用的时候,只需要将这两个文件复制到工程目录,然后包含头文件cJSON.h即可,如下:

1
#include "cJSON.h"

Tips:

我拉取的时候,最新一次提交是这个:DaveGamble/cJSON at 12c4bf1986c288950a3d06da757109a6aa1ece38

image-20241220225355333

3. 源码编译

下载完源码后,仓库是有提供demo的,我们可以直接编译的,说明文档在这里:GitHub - DaveGamble/cJSON: Ultralightweight JSON parser in ANSI C

1
2
3
4
5
6
7
# 生成Makefile文件
mkdir build
cd build
cmake ..

# 编译
make

但是其实可以直接在源码目录下执行make命令进行编译,编译完毕会得到一个名为 cJSON_test 的可执行文件,我们直接./就可以运行。

二、cJSON源码说明

1. cJSON数据结构

cJSON使用cJSON结构体来表示一个JSON数据,定义在 cJSON.h中,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 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;

首先,它不是将一整段JSON数据抽象出来,而是将其中的一条JSON数据抽象出来,也就是一个键值对,用上面的结构体 strcut cJSON 来表示,其中用来存放值的成员列表如下:

1
2
3
4
5
6

int type; // 用于表示该键值对中值的类型
char *valuestring; // 如果键值类型(type)是字符串,则将该指针指向键值
int valueint; // 如果键值类型(type)是整数,则将该指针指向键值
double valuedouble; // 如果键值类型(type)是浮点数,则将该指针指向键值
char *string; // 用于表示该键值对的名称

其次,一段完整的JSON数据中由很多键值对组成,并且涉及到键值对的查找、删除、添加,所以使用链表来存储整段JSON数据,如上面的代码所示:

1
2
struct cJSON *next; // 指向下一个键值对
struct cJSON *prev; // 指向上一个键值对

最后,因为JSON数据支持嵌套,所以一个键值对的值会是一个新的JSON数据对象(一条新的链表),也有可能是一个数组,方便起见,在cJSON中,数组也表示为一个数组对象,用链表存储,所以:

1
struct cJSON *child; // 在键值对结构体中,当该键值对的值是一个嵌套的JSON数据或者一个数组时,由child指针指向该条新链表。

2. JSON数据封装

封装JSON数据的过程,其实就是创建链表和向链表中添加节点的过程。

1
cJSON *root = NULL;
1
root = cJSON_CreateObject();
1
2
3
4
5
6
7
8
9
10
11
12
/* Helper functions for creating and adding items to an object at the same time.
* They return the added item or NULL on failure. */
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);

3. 输出JSON数据

一段完整的JSON数据就是一条长长的链表,那么,如何打印出这段JSON数据呢?cJSON提供了一个API,可以将整条链表中存放的JSON信息输出到一个字符串中:cJSON/cJSON.c at master · DaveGamble/cJSON · GitHub

1
2
3
4
5
/* Render a cJSON item/entity/structure to text. */
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item)
{
return (char*)print(item, true, &global_hooks);
}

print函数定义在这里:cJSON/cJSON.c at master · DaveGamble/cJSON · GitHub。这里就不详细去了解了,就是一个遍历链表的过程。

使用cJSON_Print()函数打印JSON数据的时候,只需要接收该函数返回的指针地址即可。

4. JSON数据解析

解析JSON数据的过程,其实就是剥离一个一个链表节点(键值对)的过程。

  • (1)创建链表头指针
1
cJSON* root = NULL;
  • (2)解析整段JSON数据,并将链表头结点地址返回,赋值给头指针
1
2
//CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); // 解析整段数据使用的API只有这一个
root = cJSON_Parse(JSON_data);
  • (3)根据键值对的名称从链表中取出对应的值,返回该键值对(链表节点)的地址
1
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string)
  • (4)如果JSON数据的值是数组,使用下面的两个API提取数据
1
2
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);

三、使用实例

1. JSON数据封装实例

1.1 JSON数据

这里设计一个JSON的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"name": "sumu",
"age": 28,
"weight": 69.8,
"address":
{
"country": "China",
"city": "shanghai",
"zip-code": 999999
},
"skill": ["c", "html", "Python"],
"student": false
}

1.2 代码编写

这里只写主函数吧,剩下的就是cJSON.ccJSON.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include "cJSON.h"

int main(int argc, const char * argv[])
{
cJSON* root = NULL;
cJSON* cjson_address = NULL;
cJSON* cjson_skill = NULL;
char* str = NULL;

/* 创建一个JSON数据对象(链表头结点) */
root = cJSON_CreateObject();

/* 添加一条字符串类型的JSON数据(添加一个链表节点) */
cJSON_AddStringToObject(root, "name", "sumu");

/* 添加一条整数类型的JSON数据(添加一个链表节点) */
cJSON_AddNumberToObject(root, "age", 28);

/* 添加一条浮点类型的JSON数据(添加一个链表节点) */
cJSON_AddNumberToObject(root, "weight", 69.8);

/* 添加一个嵌套的JSON数据(添加一个链表节点) */
cjson_address = cJSON_CreateObject();
cJSON_AddStringToObject(cjson_address, "country", "China");
cJSON_AddStringToObject(cjson_address, "city", "shanghai");
cJSON_AddNumberToObject(cjson_address, "zip-code", 999999);
cJSON_AddItemToObject(root, "address", cjson_address);

/* 添加一个数组类型的JSON数据(添加一个链表节点) */
cjson_skill = cJSON_CreateArray();
cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "C" ));
cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "html" ));
cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "Python" ));
cJSON_AddItemToObject(root, "skill", cjson_skill);

/* 添加一个值为 False 的布尔类型的JSON数据(添加一个链表节点) */
cJSON_AddFalseToObject(root, "student");

/* 打印JSON对象(整条链表)的所有数据 */
str = cJSON_Print(root);
printf("%s\n", str);

return 0;
}

1.3 编译源码

1
gcc cJSON.c main.c -o cJSON_test

1.4 运行测试

我们直接执行即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
sumu@sumu-virtual-machine:~/7Linux/cJSON-master$ ./cJSON_test
{
"name": "sumu",
"age": 28,
"weight": 69.8,
"address": {
"country": "China",
"city": "shanghai",
"zip-code": 999999
},
"skill": ["C", "html", "Python"],
"student": false
}

1.5 数据链表关系图

我根据自己的理解画了个简图如下,来帮助理解:

image-20241222170030853

2. JSON数据解析实例

2.1 JSON数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
char *JSON_data = 
"{ \
\"name\":\"sumu\", \
\"age\": 28, \
\"weight\": 69.8, \
\"address\": \
{ \
\"country\": \"China\",\
\"city\": \"shagnhai\",\
\"zip-code\": 999999\
}, \
\"skill\": [\"c\", \"html\", \"Python\"],\
\"student\": false \
}";

2.2 代码编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <stdio.h>

#include "../cJSON/cJSON.h"

char *JSON_data =
"{ \
\"name\":\"sumu\", \
\"age\": 28, \
\"weight\": 69.8, \
\"address\": \
{ \
\"country\": \"China\",\
\"city\": \"shagnhai\",\
\"zip-code\": 999999\
}, \
\"skill\": [\"c\", \"html\", \"Python\"],\
\"student\": false \
}";

int main(void)
{
cJSON* root = NULL;

cJSON* cjson_name = NULL;
cJSON* cjson_age = NULL;
cJSON* cjson_weight = NULL;

cJSON* cjson_address = NULL;
cJSON* cjson_address_country = NULL;
cJSON* cjson_address_city = NULL;
cJSON* cjson_address_zipcode = NULL;

cJSON* cjson_skill = NULL;
cJSON* cjson_skill_item = NULL;
int skill_array_size = 0;
int i = 0;

cJSON* cjson_student = NULL;

/* 解析整段JSO数据 */
root = cJSON_Parse(JSON_data);
if(root == NULL)
{
printf("parse fail.\n");
return -1;
}

/* 依次根据名称提取JSON数据(键值对) */
cjson_name = cJSON_GetObjectItem(root, "name");
cjson_age = cJSON_GetObjectItem(root, "age");
cjson_weight = cJSON_GetObjectItem(root, "weight");

printf("name: %s\n", cjson_name->valuestring);
printf("age:%d\n", cjson_age->valueint);
printf("weight:%.1f\n", cjson_weight->valuedouble);

/* 解析嵌套json数据 */
cjson_address = cJSON_GetObjectItem(root, "address");
cjson_address_country = cJSON_GetObjectItem(cjson_address, "country");
cjson_address_city = cJSON_GetObjectItem(cjson_address, "city");
cjson_address_zipcode = cJSON_GetObjectItem(cjson_address, "zip-code");
printf("address-country:%s\naddress-city:%s\naddress-zipcode:%d\n",
cjson_address_country->valuestring, cjson_address_city->valuestring, cjson_address_zipcode->valueint);

/* 解析数组 */
cjson_skill = cJSON_GetObjectItem(root, "skill");
skill_array_size = cJSON_GetArraySize(cjson_skill);
printf("skill:[");
for(i = 0; i < skill_array_size; i++)
{
cjson_skill_item = cJSON_GetArrayItem(cjson_skill, i);
printf("%s,", cjson_skill_item->valuestring);
}
printf("\b]\n");

/* 解析布尔型数据 */
cjson_student = cJSON_GetObjectItem(root, "student");
if(cjson_student->valueint == 0)
{
printf("student: false\n");
}
else
{
printf("student:error\n");
}

return 0;
}

2.3 编译源码

1
gcc cJSON.c main.c -o cJSON_test

2.4 运行测试

1
2
3
4
5
6
7
8
9
sumu@sumu-virtual-machine:~/7Linux/cJSON-master$ ./cJSON_test 
name: sumu
age:28
weight:69.8
address-country:China
address-city:shagnhai
address-zipcode:999999
skill:[c,html,Python]
student: false

参考资料

cJSON使用详细教程 | 一个轻量级C语言JSON解析器-CSDN博客

C/C++ 使用cjson库 操作Json格式文件(创建、插入、解析、修改、删除)_c++ json-CSDN博客