本套教程包括两部分,cJSON的使用介绍以及从JSON的源码解析,这里是其第一部分内容,第二部分在这里
这篇文章中的例子参考了这篇文章,并在一定程度上进行了修改。
JSON其实就是一个独立于任何编程语言的独立的轻量的数据交换的东西,方便于人的阅读和机器的解析。里面的内容还是键值对的形式存在的,可以对其进行增删改查。
JSON 格式常用于网络传输, 相对于 xml 格式, 存储需要的内存更小。
JSON 语法是 JavaScript 对象表示法语法的子集。 数据在键/值对中; 数据由逗号分隔; 花括号保存对象, 也称一个文档对象; 方括号保存数组, 每个数组成员用逗号隔开, 并且每个数组成员可以是文档对象或者数组或者键值对 。
JSON 的三种语法:
1.键/值对 key:value, 用半角冒号分割。 比如 “name”:“Faye”
2.文档对象 JSON ,对象写在花括号中, 可以包含多个键/值对。 比如{ “name”:“Faye” ,“address”:“北京” }。
3.数组 JSON ,数组在方括号中书写: 数组成员可以是对象, 值, 也可以是数组(只要有意义)。 {“love”: [“乒乓球”,“高尔夫”,“斯诺克”,“羽毛球”,“LOL”,“撩妹”]}
cJSON 开源项目位置:项目所在位置
json在线解析器:项目所在位置 , 可以用来检查输入的json数据是否符合语法:
cJSON项目包括三个文件。test.c(下图的main.c)、cJSON.h、cJSON.c
接下来将通过几个简单的例子,对cJSON的API使用进行介绍
创建json格式的数据串,这个串可以是对象(object,大括号标识{ },成员之间由逗号隔开),可以是数组(array,中括号标识[ ],成员之间由逗号隔开)。
<例1>创建对象,向对象里面添加字符串和数值,这里有两级object:
int main (int argc, const char * argv[]) {
/* Our "Video" datatype: */
root=cJSON_CreateObject();// 类似于创建了一个大括号
//向root添加结点,"name"为键,"Jack (\"Bee\") Nimble")为对应的值
cJSON_AddItemToObject(root, "name", cJSON_CreateString("Jack (\"Bee\") Nimble"));
//向root添加结点,"format"为键,fmt为对应的值,这里是一个object的结构
cJSON_AddItemToObject(root, "format", fmt=cJSON_CreateObject());
//向fmt添加结点,"type"为键,"rect"为对应的值
cJSON_AddStringToObject(fmt,"type", "rect");
//添加结点,"width"为键,1920为对应的值
cJSON_AddNumberToObject(fmt,"width", 1920);
//向fmt添加结点,"height"为键,1080为对应的值
cJSON_AddNumberToObject(fmt,"height", 1080);
//向fmt添加结点,"interlace"为键,函数会自动创建键值为"false"
cJSON_AddFalseToObject (fmt,"interlace");
//向fmt添加结点,"frame rate"为键,24为对应的键
cJSON_AddNumberToObject(fmt,"frame rate", 24);
out=cJSON_Print(root); // 将cJSON结构的内容,组织成字符串,out指向这部分内存
cJSON_Delete(root); //释放由malloc申请的关于结构方面的动态内存
printf("%s\n",out); //打印
free(out); //释放malloc申请的字符串的动态内存
return 0;
}
说明
1.上面关于给出了代码的注释,可以参照理解程序
2.cJSON_CreateObject函数创建object,之后便可向该根数据项中添加字符串、数值型、array以及object等内容,返回 cJSON的指针,需要注意的是需要调用cJSON_Delete(root); 对由malloc申请的动态内存进行回收。
3.cJSON_Delete只释放了cJSON结构以及cJSON结构成员动态申请的内存,各个结构组合在一起的字符串的内存由变量out指向,依旧需要调用free释放内存。
这段代码创建的结构链如下:
暂时不懂为什么是这样没有什么关系,阅读第二篇cJSON源码详解后,相信你会彻底明白这些。
<例2>创建数组,向数组里面添加字符串和数值:
int main (int argc, const char * argv[]) {
cJSON *root;
root = cJSON_CreateArray(); //创建一个array
cJSON_AddItemToArray(root, cJSON_CreateString("Yuan"));
cJSON_AddItemToArray(root, cJSON_CreateNumber(1314));
// 两种打印方式,这一种打印方式会进行格式化输出\n,\t之类的
char *s1 = cJSON_Print(root);
// 直接输出一句字符串,不加入格式
char *s2 = cJSON_PrintUnformatted(root);
// 判断是否创建成功,成功的话释放内存
if(root)
cJSON_Delete(root);
// 判断是否转化为字符串成功,成功的话打印并释放动态内存
if(s1)
{
printf(" 带有格式的输出结果:\n%s \n",s1);
free(s1);
}
// 判断是否转化为字符串成功,成功的话打印并释放动态内存
if(s2)
{
printf(" 不带格式的输出结果:\n%s \n",s2);
free(s2);
}
return 0;
输出结果:
从这里可以看出,带格式的打印两个单元之间,明显比不带格式的打印多了空白字符,这里区分不大,下一个例子会比较明显。
<例3>对象里面包括一个数组,数组里面包括对象,对象里面再添加一个字符串和一个数字:
int main (int argc, const char * argv[]) {
cJSON *root, *array,*subObject;
root = cJSON_CreateObject(); // 创建一个object,一个大括号{}
// 在root结点下面添加新节点,结点的键为"information",值为一个array的数组[ ]
cJSON_AddItemToObject(root,"information", array = cJSON_CreateArray());
// 在array结点下添加新节点,新节点为一个object对象
cJSON_AddItemToArray(array, subObject = cJSON_CreateObject());
// 在subObject下面添加新节点,键的内容为"lastname",值为"Yuan"
cJSON_AddStringToObject(subObject,"lastname","Yuan");
// 在subObject下面添加新节点,键的内容为"lastname",值为100
cJSON_AddNumberToObject(subObject,"ID",100);
char *s1 = cJSON_Print(root); // 带有格式的打印,换行符、制表符、空格等
char *s2 = cJSON_PrintUnformatted(root); // 不带格式的打印,效果可以看后面的输出结果
if(root)
cJSON_Delete(root);
// 判断是否转化为字符串成功,成功的话打印并释放动态内存
if(s1)
{
printf(" 带有格式的输出结果:\n%s \n",s1);
free(s1);
}
// 判断是否转化为字符串成功,成功的话打印并释放动态内存
if(s2)
{
printf(" 不带格式的输出结果:\n%s \n",s2);
free(s2);
}
return 0;
输出结果:
从这里可以明显的看出带格式和不带格式打印字符的区别:
这段代码的内容,可以结合注释以及例1给出链进行理解
创建json结构的过程就像搭积木一样,需要注意的是父子之间的关系,同时注意区分array结点和object节点和普通数据结点之间的区别。
json数据的解析主要是通过cJSON_Parse函数实现的,是前面例子的逆过程,指定键,从对应的cJSON结构体中提取对应的值
text = "{\n\"name\": \"Jack (\\\"Bee\\\") Nimble\", \n\"format\": {\"type\": \"rect\", \n\"width\": 1920, \n\"height\": 1080, \n\"interlace\": false,\"frame rate\": 24\n}\n}"
json=cJSON_Parse(text);//通过此函数将字符串存储到cJSON的结构块中,并且结构块之间相连
<例1>字符串之间不存在父节点所有的成员都属于同一级别:
int main (int argc, const char * argv[]) {
cJSON *json,*jsonName,*jsonPasswd,*jsonNum;
char* out="{\"name\":\"fengxin\",\"passwd\":\"123\",\"num\":1}";
json = cJSON_Parse(out); //解析成json形式
if(!json)
return 0;
jsonName = cJSON_GetObjectItem( json , "name" ); //获取指定的键对应的内容
jsonPasswd = cJSON_GetObjectItem( json , "passwd" );//获取指定的键对应的内容
jsonNum = cJSON_GetObjectItem( json , "num" );//获取指定的键对应的内容
printf("%s","输出结果如下:\n");
printf("name:%s\npasswd:%s\nnum:%d\n",jsonName->valuestring,jsonName->valuestring,jsonNum->valueint);
cJSON_Delete(json); //释放内存
return 0;
说明:
这里在打印值的时候,不同类型的键值对使用了结构体中不同的成员,比如打印数字使用:valueint;打印字符串使用:valuestring,这是建立在对cJSON结构了解的基础上,实际上需要对cJSON结构体成员中type变量判断,在进行打印,代码方面的细节看cJSON源码分析
结果如下:
完成了对json数据的解析,可以通过指定键获取其对应的值:
<例2>一个父object中包含两个子object,子object存储具体内容,即存在父层结构:
int main (int argc, const char * argv[]) {
char *s = "{\"list\":{\"name\":\"xiao hong\",\"age\":10},\"other\":{\"name\":\"hua hua\"}}";
cJSON *root = cJSON_Parse(s); //将字符串转化为对应的结构
if(!root) { // 转化出错返回
printf("get root faild !\n");
return -1;
}
// 获取子object单元
cJSON *js_list = cJSON_GetObjectItem(root, "list");
if(!js_list) { //判断是否成功
printf("no list!\n");
return -1;
}
// 打印子object的类型,输出为6因为object的type被宏定义为6
printf("第一个子object类型是: %d\n",js_list->type);
// 从子object根据指定的键输出对应的内容
cJSON *name = cJSON_GetObjectItem(js_list, "name");
if(!name) {
printf("No name !\n");
return -1;
}
// 输出为4,字符串的结构的type被定义为4
printf("第一个子object的第一个成员的类型是: %d\n",name->type);
printf("对应的值是 %s\n",name->valuestring);
//从子object根据指定的键输出对应的内容
cJSON *age = cJSON_GetObjectItem(js_list, "age");
if(!age) {
printf("no age!\n");
return -1;
}
//输出为3,字符串的结构的type被定义为3
printf("第一个子object的第二个成员的类型是: %d\n", age->type);
printf("对应的值是 %d\n",age->valueint);
//获取另一个子object
cJSON *js_other = cJSON_GetObjectItem(root, "other");
if(!js_other) {
printf("no list!\n");
return -1;
}
// 打印子object的类型,输出为6因为object的type被宏定义为6
printf("第二个子object类型是: %d\n",js_other->type);
cJSON *js_name = cJSON_GetObjectItem(js_other, "name");
if(!js_name) {
printf("No name !\n");
return -1;
}
// 输出为4,字符串的结构的type被定义为4
printf("第二个子object的第一个成员的类型是: %d\n",js_name->type);
printf("对应的值是 %s\n",js_name->valuestring);
if(root) //释放内存
cJSON_Delete(root);
return 0;
}
结果如下:
从这段代码可以看出实际上具有父节点的json结构只需要逐层提取就可以,就像剥洋葱一样,一层层的剥开
<例3>提取数组中的内容:
int main (int argc, const char * argv[]) {
char *s = "{\"list\":[\"name1\",\"name2\"]}";
cJSON *root = cJSON_Parse(s);
if(!root) {
printf("get root faild !\n");
return -1;
}
cJSON *js_list = cJSON_GetObjectItem(root, "list");
if(!js_list){
printf("no list!\n");
return -1;
}
//获取数组的大小
int array_size = cJSON_GetArraySize(js_list);
printf("数组的大小是 %d\n",array_size);
int i = 0;
cJSON *item;
for(i=0; i< array_size; i++) {
//获取指定位置的cJSON结构体
item = cJSON_GetArrayItem(js_list, i);
printf("结构的类型是 %d\n",item->type);
printf("对应的值是 %s\n",item->valuestring);
}
// 释放内存
if(root)
cJSON_Delete(root);
return 0;
}
说明
这里可以看出在数组array中获取元素和在object获取元素的区别,在array是通过其在数组中的位置来获取,而在object中则是根据键值匹配获取,实际上object对象的获取调用了array的方法,二者都是通过位置来获取对应的值
输出结果为
char *s = "{\"list\":[{\"name\":\"xiao hong\",\"age\":10},{\"name\":\"hua hua\",\"age\":11}]}";
cJSON *root = cJSON_Parse(s);
if(!root) {
printf("get root faild !\n");
return -1;
}
//获取父节点"list"下的array
cJSON *js_list = cJSON_GetObjectItem(root, "list");
if(!js_list){
printf("no list!\n");
return -1;
}
// 计算数组的大小
int array_size = cJSON_GetArraySize(js_list);
printf("数组的大小是 %d\n",array_size);
int i = 0;
cJSON *item,*it, *js_name, *js_age;
char *p = NULL;
for(i=0; i< array_size; i++) {
item = cJSON_GetArrayItem(js_list, i);
if(!item) {
return -1;
printf("%s\n","出错了!");
}
// 根据子object得到其对应的字符串
p = cJSON_PrintUnformatted(item);
// 对子object的字符串创建一条新的数据链
it = cJSON_Parse(p);
if(!it)
continue ;
js_name = cJSON_GetObjectItem(it, "name");
printf("名字是 %s\n",js_name->valuestring);
js_age = cJSON_GetObjectItem(it, "age");
printf("年龄是 %d\n",js_age->valueint);
free(p); // 释放运行过程中产生的动态内存,调用cJSON_PrintUnformatted产生
cJSON_Delete(it); // // 释放运行过程中产生的动态内存,调用 cJSON_Parse(p)产生
}
if(root)
cJSON_Delete(root);
return 0;
说明
根据这个例子我们可以看到,对于嵌套了子object的结构,我们可以调用cJSON_PrintUnformatted获取子object对应的字符串,
输出结果
至此json解析的部分结束,想要更深层次的了解源码请看这里。