一款轻量级的JSON解析库,用cJSON让你看清王者荣耀
2021年的第一篇文章,带你看清王者荣耀
JSON是一种轻量级的数据格式,应用广泛。在C/C++应用中也常常作为配置文件或者数据的存储
JSON语法规则
JSON对象是一个无序的"名称/值"键值对的集合:
- 以"
{
“开始,以”}
"结束,允许嵌套使用; - 每个名称和值成对出现,名称和值之间使用"
:
"分隔; - 键值对之间用"
,
"分隔 - 在这些字符前后允许存在无意义的空白符;
对于键值,可以有如下值:
- 一个新的json对象
- 数组:使用"
[
“和”]
"表示 - 数字:直接表示,可以是整数,也可以是浮点数
- 字符串:使用引号
"
表示 - 字面值:false、null、true中的一个(必须是小写)
示例如下:
[{
"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
cJSON下载使用
cJSON是使用ANSI C编写的超轻量级的JSON解析器,因此在C中也常常是不二之选。
cJSON项目托管在Github上,仓库地址如下:
使用Git命令将其拉取到本地:
git clone https://github.com/DaveGamble/cJSON.git
从Github拉取cJSON源码后,文件非常多,但是其中cJSON的源码文件只有两个:
cJSON.h
cJSON.c
使用的时候,只需要将这两个文件复制到工程目录,然后包含头文件cJSON.h
即可,如下:
#include "cJSON.h"
详细使用视频:VS使用cJSON库
关键数据结构
cJSON的关键数据结构如下:
typedef struct cJSON { //cJSON结构体
struct cJSON*next,*prev; /*后驱节点和前驱节点*/
struct cJSON *child; /*孩子节点*/
int type; /* 键的类型*/
char *valuestring; /*字符串值*/
int valueint; /* 整数值*/
double valuedouble; /* 浮点数值*/
char *string; /* 键的名字*/
} cJSON;
json是一种组织良好的数据格式,因而JSON中的内容解析后,都可以通过以上数据结构进行处理。
例如,对于下面的json内容:
{
"name":"编程学习基地",
"site":"https://www.deroy.cn",
"age":1
}
解析后,site将会是name的next节点,并且它的键类型是字符串。
cJSON数据解析
常用接口函数
用于将字符串解析成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文件内容读取到buffer
- 通过cJSON接口解析buffer中的字符串
- 获取JSON指定字段
为了将JSON文件的内容读取到buffer,需要知道文件的大小:
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中:
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;
}
再根据前面提到的解析流程,我们的JSON预解析函数如下:
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;
}
解析示例
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<string.h>
#include"cJSON.h"
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 = "herolist.json";
cJSON *pJson = NULL;
cJSON *pTemp = NULL;
cJSON *pVal = NULL;
/*创建cJSON对象*/
pJson = prepare_parse_json(filename);
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;
}
gcc -o mian main.c cJSON.c
windows下VC6.0,VS也可以运行,但是因为编码格式问题,我另写了一套程序
为了让输出看起来舒服,改了点格式,输出如下(删减),源码获取发送关键字【王者荣耀】
|ename |cname |title |new_type |hero_type |skin_name|
-----------------------------------------------------------------------------------------------------------
|105 |廉颇 |正义爆轰 |0 |3 |正义爆轰|地狱岩魂|
-----------------------------------------------------------------------------------------------------------
|106 |小乔 |恋之微风 |0 |2 |恋之微风|万圣前夜|天鹅之梦|纯白花嫁|缤纷独角兽|
-----------------------------------------------------------------------------------------------------------
|107 |赵云 |苍天翔龙 |0 |1 |苍天翔龙|忍●炎影|未来纪元|皇家上将|嘻哈天王|白执事|引擎之心|
-----------------------------------------------------------------------------------------------------------
|108 |墨子 |和平守望 |0 |2 |和平守望|金属风暴|龙骑士|进击墨子号|
-----------------------------------------------------------------------------------------------------------
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;
}
输出结果:
{
"null_test": null,
"true_test": true,
"false_test": false,
"bool_test": false,
"int_test": 22,
"double_test": 55.5,
"str_test": "我是字符串",
"key": 任意数据,
"objet": {
},
"address": {
"country": "China",
"zip-code": 111111
},
"Array": [],
"skill": ["C", "C++", "Python", "Java"]
}
完整代码:
链接:https://pan.baidu.com/s/1Pej2aKKPW5d0_cmUMgm5HQ
提取码:18re