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上,仓库地址如下:
https://github.com/DaveGamble/cJSON
使用Git命令将其拉取到本地:
git clone https://github.com/DaveGamble/cJSON.git
从Github拉取cJSON源码后,文件非常多,但是其中cJSON的源码文件只有两个:
-
cJSON.h
-
cJSON.c
使用的时候,只需要将这两个文件复制到工程目录,然后包含头文件cJSON.h
即可,如下:
#include "cJSON.h"
关键数据结构
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;elsereturn 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;elsereturn 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 = (char*)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下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"]
}