前言
众所周知,JSON是一种轻量级的数据格式,应用广泛。在C/C++应用中也常常作为配置文件或者数据的存储,因此JSON文件的生成和解析是必备知识。
cJSON
cJSON是使用ANSI C编写的超轻量级的JSON解析器,因此在C中也常常是不二之选。
github 地址:https://github.com/DaveGamble/cJSON
下载到本地后,进行编译:1
$ make
执行完成后即可在当前目录下得到libcjson.a和libcjson.so。
当然你也可以只下载cJSON.c和cJSON.h自己编译成静态库或动态库,可参考前期文章《如何制作静态库》和《动态库的制作和两种使用方式》。编译后的.a保留调试信息也只有不过43k。
关键数据结构
cJSON的关键数据结构如下:1
2
3
4
5
6
7
8
9typedef struct cJSON { //cJSON结构体
struct cJSON*next,*prev; /*后驱节点和前驱节点*/
struct cJSON *child; /*孩子节点*/
int type; /* 键的类型*/
char *valuestring; /*字符串值*/
int valueint; /* 整数值*/
double valuedouble; /* 浮点数值*/
char *string; /* 键的名字*/
} cJSON;
json是一种组织良好的数据格式,因而JSON中的内容解析后,都可以通过以上数据结构进行处理。
例如,对于下面的json内容:1
2
3
4
5{
"name":"编程珠玑",
"site":"https://www.yanbinghu.com",
"age":1
}
解析后,site将会是name的next节点,并且它的键类型是字符串。
常用接口函数
1 | cJSON *cJSON_Parse(const char *value); |
用于将字符串解析成json对象,若失败则返回NULL。
1 | cJSON *cJSON_GetObjectItem(cJSON *object,const char *string); |
用于获取json对象中的某个节点,若失败,返回NULL,成功则返回该节点对象。
1 | void cJSON_Delete(cJSON *c); |
用于释放json对象相关内存。
1 | char *cJSON_Print(const cJSON *item); |
用于将JSON对象转换成字符串,记得最后释放相关内存。
JSON文件解析准备
解析JSON文件可大致分为以下几个步骤:
- 获取文件大小
- 将JSON文件内容读取到buffer
- 通过cJSON接口解析buffer中的字符串
- 获取JSON指定字段
为了将JSON文件的内容读取到buffer,需要知道文件的大小:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//来源:公众号【编程珠玑】
//https://www.yanbinghu.com
size_t get_file_size(const char *filepath)
{
/*check input para*/
if(NULL == filepath)
return 0;
struct stat filestat;
memset(&filestat,0,sizeof(struct stat));
/*get file information*/
if(0 == stat(filepath,&filestat))
return filestat.st_size;
else
return 0;
}
然后申请一段内存,将文件中的文本读取到buffer中: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//来源:公众号【编程珠玑】
//https://www.yanbinghu.com
char *read_file_to_buf(const char *filepath)
{
/*check input para*/
if(NULL == filepath)
{
return NULL;
}
/*get file size*/
size_t size = get_file_size(filepath);
if(0 == size)
return NULL;
/*malloc memory*/
char *buf = malloc(size+1);
if(NULL == buf)
return NULL;
memset(buf,0,size+1);
/*read string from file*/
FILE *fp = fopen(filepath,"r");
size_t readSize = fread(buf,1,size,fp);
if(readSize != size)
{
/*read error*/
free(buf);
buf = NULL;
return NULL;
}
buf[size] = 0;
return buf;
}
再根据前面提到的解析流程,我们的JSON预解析函数如下:
1 | cJSON *prepare_parse_json(const char *filePath) |
来源:公众号【编程珠玑】
网站:https://www.yanbinghu.com
解析JSON文件
假设我们的JSON文件内容如下:1
2
3
4
5{
"name":"编程珠玑",
"site":"https://www.yanbinghu.com",
"age":1
}
按照我们前面梳理的解析JSON的步骤,main函数代码如下: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
26int main(void)
{
char *filename = "./test.json";
cJSON *pJson = NULL;
cJSON *pTemp = NULL;
pJson = prepare_parse_json(filename);
if(NULL == pJson)
{
printf("parse json failed\n");
return -1;
}
/*获取name值*/
pTemp = cJSON_GetObjectItem(pJson,"name");
printf("name is %s\n",pTemp->valuestring);
/*获取site值*/
pTemp = cJSON_GetObjectItem(pJson,"site");
printf("site is %s\n",pTemp->valuestring);
/*获取age值*/
pTemp = cJSON_GetObjectItem(pJson,"age");
printf("age is %d\n",pTemp->valueint);
/*记得释放相关内存*/
cJSON_Delete(pJson);
pJson = NULL;
return 0;
}
从上面看来,我们自己封装好之后,解析json似乎也没有那么复杂?
编译:1
gcc -L . -o parseJson parseJson.c -lcjson
注意指定链接cjson库的路径。
运行:1
2
3
4$ ./parseJson
name is 编程珠玑
site is https://www.yanbinghu.com
age is 1
写JSON
写JSON又该如何呢?实际上不过是组装JSON对象罢了。示例代码如下: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#include<stdio.h>
#include<stdlib.h>
#include"cJSON.h"
int main(void)
{
cJSON *pRoot = NULL;
char *json = NULL;
cJSON *array = NULL;
//创建根节点对象
pRoot = cJSON_CreateObject();
//向根节点加入数字对象
cJSON_AddNumberToObject(pRoot, "age", 1);
//向根节点加入字符串对象
cJSON_AddStringToObject(pRoot, "name", "编程珠玑");
//创建数组对象
array = cJSON_CreateArray();
cJSON_AddStringToObject(array,"language","C");
cJSON_AddStringToObject(array,"language","C++");
//向根节点中添加数组
cJSON_AddItemToObject(pRoot,"array",array);
//解析成字符串
json = cJSON_Print(pRoot);
printf("%s\n", json);//这里也可以将字符串写入文件
//释放json对象的空间
cJSON_Delete(pRoot);
pRoot = NULL;
//记得释放json的空间
free(json);
json = NULL;
return 0;
}
运行结果:1
2
3
4
5{
"age": 1,
"name": "编程珠玑",
"array": ["C", "C++"]
}
总结
相比于python一条搞定解析来说,C语言中解析JSON似乎显得有些麻烦,但cJSON无疑是一个超轻量级的JSON器。
附录
完整解析代码: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
90
91
92
93
94
95
96
97
98
99
100//来源:公众号【编程珠玑】
//https://www.yanbinghu.com
size_t get_file_size(const char *filepath)
{
/*check input para*/
if(NULL == filepath)
return 0;
struct stat filestat;
memset(&filestat,0,sizeof(struct stat));
/*get file information*/
if(0 == stat(filepath,&filestat))
return filestat.st_size;
else
return 0;
}
char *read_file_to_buf(const char *filepath)
{
/*check input para*/
if(NULL == filepath)
{
return NULL;
}
/*get file size*/
size_t size = get_file_size(filepath);
if(0 == size)
return NULL;
/*malloc memory*/
char *buf = malloc(size+1);
if(NULL == buf)
return NULL;
memset(buf,0,size+1);
/*read string from file*/
FILE *fp = fopen(filepath,"r");
size_t readSize = fread(buf,1,size,fp);
if(readSize != size)
{
/*read error*/
free(buf);
buf = NULL;
}
buf[size] = 0;
return buf;
}
cJSON *prepare_parse_json(const char *filePath)
{
/*check input para*/
if(NULL == filePath)
{
printf("input para is NULL\n");
return NULL;
}
/*read file content to buffer*/
char *buf = read_file_to_buf(filePath);
if(NULL == buf)
{
printf("read file to buf failed\n");
return NULL;
}
/*parse JSON*/
cJSON *pTemp = cJSON_Parse(buf);
free(buf);
buf = NULL;
return pTemp;
}
int main(void)
{
char *filename = "./test.json";
cJSON *pJson = NULL;
cJSON *pTemp = NULL;
pJson = prepare_parse_json(filename);
if(NULL == pJson)
{
printf("parse json failed\n");
return -1;
}
/*获取name值*/
pTemp = cJSON_GetObjectItem(pJson,"name");
printf("name is %s\n",pTemp->valuestring);
/*获取site值*/
pTemp = cJSON_GetObjectItem(pJson,"site");
printf("site is %s\n",pTemp->valuestring);
/*获取age值*/
pTemp = cJSON_GetObjectItem(pJson,"age");
printf("age is %d\n",pTemp->valueint);
/*记得释放相关内存*/
cJSON_Delete(pJson);
pJson = NULL;
return 0;
}