一、json
JSON(JavaScript Object Notation,JavaScript对象表示法,读作/ˈdʒeɪsən/)是一种由道格拉斯·克罗克福特构想和设计、轻量级的数据交换语言,该语言以易于让人阅读的文字为基础,用来传输由属性值或者序列性的值组成的数据对象。尽管JSON是JavaScript的一个子集,但JSON是独立于语言的文本格式,并且采用了类似于C语言家族的一些习惯。
JSON的基本数据类型:
数值:十进制数,不能有前导0,可以为负数,可以有小数部分。还可以用e或者E表示指数部分。不能包含非数,如NaN。不区分整数与浮点数。JavaScript用双精度浮点数表示所有数值。
字符串:以双引号""括起来的零个或多个Unicode码位。支持反斜杠开始的转义字符序列。
布尔值:表示为true或者false。
值的有序列表(array):有序的零个或者多个值。每个值可以为任意类型。序列表使用方括号[,]括起来。元素之间用逗号,分割。形如:[value, value]
对象(object):一个无序的“键-值对”(pair),其中键是字符串。建议但不强制要求对象中的键是独一无二的。对象以花括号{开始,并以}结束。键-值对之间使用逗号分隔。键与值之间用冒号:分割。
null类型:值写为null
token(6种标点符号、字符串、数值、3种字面量)之间可以存在有限的空白符并被忽略。四个特定字符被认为是空白符:空格符、水平制表符、回车符、换行符。空白符不能出现在token内部(但空格符可以出现在字符串内部)。JSON标准不允许有字节序掩码,不提供注释的句法。 一个有效的JSON文档的根节点必须是一个对象或一个数组。
具体格式的定义大家可以参考这个链接https://tools.ietf.org/html/rfc4627
举例:
{
"firstName": "John",
"lastName": "Smith",
"sex": "male",
"age": 25,
"address":
{
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021"
},
"phoneNumber":
[
{
"type": "home",
"number": "212 555-1234"
},
{
"type": "fax",
"number": "646 555-4567"
}
]
}
json的[]代表着数组的格式,数组每个{}中间的数据就是数组中的一个元素也就是一个对象,json文件的后缀名为:.json。
二、cjson
1、认识cjson
上面学习了json数据格式的定义,下面我们就来学习下如何使用cjson库。cjson能够帮我们解析和打包,这样就给开发人员很大的便利了,我们首先下载好cjson库
git clone https://github.com/arnoldlu/cJSON.git
这样我们就有了cjson的头文件和c文件了,接下来我们可以了解下cjson的大致内容和重要函数的接口,整个库里最重要的就是存储信息的这个结构体
typedef struct cJSON
{ //cJSON结构体
struct cJSON *next,*prev; /* 遍历数组或对象链的前向或后向链表指针*/
struct cJSON *child; /* 数组或对象的孩子节点*/
int type; /* key的类型*/
char *valuestring; /* 字符串值*/
int valueint; /* 整数值*/
double valuedouble; /* 浮点数值*/
char *string; /* key的名字*/
} cJSON;
cJSON对象的实现采用了树形结构,每个对象是树的一个节点,每个节点由cJSON这个结构体组成,对象中的元素也由cJSON这个结构体组成。同一层的对象和元素是双向链表结构,由next和prev指针链接。不同层的对象或元素由child指针链接起来。type表示对象或元素类型,string表示对象或节点的名称。元素的值存储在valuestring, valueint和valuedouble中。
cjson常用的函数
cJSON *cJSON_Parse(const char *value);
作用:将一个JSON数据包,按照cJSON结构体的结构序列化整个数据包,并在堆中开辟一块内存存储cJSON结构体
返回值:成功返回一个指向内存块中的cJSON的指针,失败返回NULL
cJSON *cJSON_GetObjectItem(cJSON *object,const char *string);
作用:获取JSON字符串字段值
返回值:成功返回一个指向cJSON类型的结构体指针,失败返回NUL
char *cJSON_Print(cJSON *item);
作用:将cJSON数据解析成JSON字符串,并在堆中开辟一块char*的内存空间存储JSON字符串
返回值:成功返回一个char*指针该指针指向位于堆中JSON字符串,失败返回NULL
void cJSON_Delete(cJSON *c);
作用:释放位于堆中cJSON结构体内存
返回值:无
void cJSON_AddItemToArray(cJSON *array, cJSON *item);
void cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
作用:将每一项的内容附加到指定的数组/对象
返回值:无
/* These calls create a cJSON item of the appropriate type. */
extern cJSON *cJSON_CreateNull(void);
extern cJSON *cJSON_CreateTrue(void);
extern cJSON *cJSON_CreateFalse(void);
extern cJSON *cJSON_CreateBool(int b);
extern cJSON *cJSON_CreateNumber(double num);
extern cJSON *cJSON_CreateString(const char *string);
cJSON_GetArraySize(const cJSON *array);
//Returns the number of items in an array (or object)
cJSON *cJSON_GetArrayItem(const cJSON *array, int item);
//Retrieve item number "item" from array "array". Returns NULL if unsuccessful
2、cjson打包数据
上面一些都是在创建或解析json文件所要用到的放法,我们先来创建一个比较简单的例子。json的数据如下
{
"name": "taylor swift",
"age": 31,
"album": "Lover",
"song": "Miss Americana & The Heartbreak Prince"
}
#include <stdio.h>
#include "cJSON.h"
int main()
{
cJSON * root = cJSON_CreateObject();
cJSON_AddItemToObject(root, "name", cJSON_CreateString("Taylor Swift"));//根节点下添加
cJSON_AddItemToObject(root, "age", cJSON_CreateNumber(31));
cJSON_AddItemToObject(root, "album", cJSON_CreateString("Lover"));
cJSON_AddItemToObject(root, "song", cJSON_CreateString("Miss Americana & The Heartbreak Prince"));
printf("%s\n", cJSON_Print(root));
return 0;
}
上述json的格式所有的键对值都是在根节点下的,所以我们使用cJSON_AddItemToObject函数的第一个参数为同一个节点,也就是根节点。
第二个参数就是没想数据的key的名称,第三个参数为key对应的值的类型,不同类型用不同的函数。
gcc cJSON.c cjson_study_1.c -lm
上面简单的我们已经创建完毕,这时我们可以挑战一下更高难度的,有数组有嵌套数据,实际的编码和上面的差不多,只不过多了个嵌套和数组,数组的话我们就可以用新的函数来实现,下面为一个json对象实例
{
"name": "Taylor Swfit",
"age": 31,
"album":{
"1989":"Style",
"Reputation":[
{
"song":"Ready For It"
},
{
"song":"End Game"
}
],
"Lover":"Miss Americana & The Heartbreak Prince"
},
"address":"Tennessee"
}
下面则是代码,具体的思路如下,我们可以首先观察数据有几层(数组也可以算一层)格式,成对的{}出现(数组[]里的{}不算)和[]成对的出现,连个成对数目的相加就表示需要几个cJSON_CreateObject()所返回的指针,每个数组([]成对的)又需要一个额外的cJSON_CreateObject()返回的指针。具体的实现可以参考我下面的代码。
然后我们可以首先从最里面的一层也就是数组开始,然后一层一层的往外推,每层的添加顺序都是从第一个开始的,具体的可以参考下代码,水平有限很多东西还无法用文字表达
#include <stdio.h>
#include "cJSON.h"
int main()
{
cJSON * root = cJSON_CreateObject();//生成的根节点
cJSON * album = cJSON_CreateObject();//album里嵌套的节点
cJSON * rep_itm = cJSON_CreateObject();//数组的里面的起始节点
cJSON * rep_arr = cJSON_CreateArray();//生成一个空数组
cJSON_AddItemToObject(rep_itm, "song", cJSON_CreateString("Ready For It"));//数组的第一个元素
cJSON_AddItemToObject(rep_itm, "song", cJSON_CreateString("End Game"));//数组的第二个元素
cJSON_AddItemToArray(rep_arr, rep_itm);//将数组的内容全部添加到这个数组的起始节点
cJSON_AddItemToObject(album, "1989", cJSON_CreateString("Style"));//album节点下的第一个键值对
//album节点下的第二个键值对,这是一个数组的信息,所以需要添加数组的信息
cJSON_AddItemToObject(album, "Reputation", rep_arr);
//albumn节点下的最后一个键值对,
cJSON_AddItemToObject(album, "Lover", cJSON_CreateString("Miss Americana & The Heartbreak Prince"));
cJSON_AddItemToObject(root, "name", cJSON_CreateString("Taylor Swift"));//根节点下添加第一个键值对
cJSON_AddItemToObject(root, "age", cJSON_CreateNumber(31));//根节点下添加第二个键值对
cJSON_AddItemToObject(root, "album", album);//节点下添加第三个键值对, album内嵌了一层,所以直接写它的指针
cJSON_AddItemToObject(root, "address", cJSON_CreateString("Tennessee"));//跟节点下添加第四个键值对
printf("%s\n", cJSON_Print(root));
return 0;
}
gcc hhhhh.c cJSON.c -lm
3、cjson解析数据
上面我们学会了利用cjson库来生成json对象,那么竟然有生成那就肯定有打包,我们先看第一个简单的数据格式,也就是打包的第一个例子。我们首先把上面打包的数据输入到一个文件中,也可以选择直接写进字符串。
{
"name": "taylor swift",
"age": 31,
"album": "Lover",
"song": "Miss Americana & The Heartbreak Prince"
}
查看文章的内容,结果是正确的,现在我们可以开始编程了。
源代码如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "cJSON.h"
#define FILENAME "debug.txt"
#define SIZE 1024
int main(int argc, char *argv[])
{
int fd = -1;
int rv = -1;
char buf[SIZE];
cJSON* cjson = NULL;
char* string1 = NULL;
char* string2 = NULL;
char* string3 = NULL;
int age = 0;
fd=open(FILENAME, O_RDWR);
if(fd < 0)
{
printf("open %s failure\n", FILENAME);
return -1;
}
rv=lseek(fd, 0, SEEK_CUR);
if(rv < 0)
{
printf("lseek %s failure\n", FILENAME);
return -1;
}
rv=read(fd, buf, sizeof(buf));
if(rv < 0)
{
printf("read %s failure\n", FILENAME);
return -1;
}
close(fd);
cjson=cJSON_Parse(buf);//将数据转换成cjson格式,记得使用cJSON_Delete(cJSON *c), 防止内存泄漏
if(cjson == NULL)//判断是否成功
{
printf("data pack to cjson failure\n");
return -1;
}
printf("%s\r\n",cJSON_Print(cjson));//打印输出cjsong格式
//cJSON_GetObjectItem(cjson,"cmd")返回的是一个结构体指针,所以我们可以通过指针知道对应的值
string1=cJSON_GetObjectItem(cjson,"name")->valuestring;
age=cJSON_GetObjectItem(cjson,"age")->valueint;
string2=cJSON_GetObjectItem(cjson,"album")->valuestring;
string3=cJSON_GetObjectItem(cjson,"song")->valuestring;
printf("name:%s\r\n", string1);
printf("age:%d\r\n", age);
printf("album:%s\r\n", string2);
printf("song:%s\r\n", string3);
cJSON_Delete(cjson);//释放内存, 一般都是在最后释放,一定要等上面的打印完,因为这些指针指向这些内存
return 0;
}
上面就是一些简单的json格式的解析,那么如果我们遇到了比较复杂的json数据,就像我们打包的第二个数据,里面有嵌套还有数组,这种复杂的数据我们该怎么解析呢。
先将json数据写入一个文本,数据如下
我们的思路是,首先找到根节点下有几个键值对,然后遍历这些键值对,每个键值对判断它是否是属于json对象,也就是是否有嵌套,有的话递归,没有的话就打印出来
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "cJSON.h"
#define FILENAME "debug_2.txt"
#define SIZE 1024
void printJson(cJSON * root)//以递归的方式打印json的最内层键值对
{
for(int i=0; i<cJSON_GetArraySize(root); i++) //遍历最外层json键值对
{
cJSON * item = cJSON_GetArrayItem(root, i);
if(cJSON_Object == item->type) //如果对应键的值仍为cJSON_Object就递归调用printJson
printJson(item);
else //值不为json对象就直接打印出键和值
{
printf("%s:", item->string);
printf("%s\r\n", cJSON_Print(item));
}
}
}
int main(int argc, char *argv[])
{
int fd = -1;
int rv = -1;
char buf[SIZE];
cJSON* cjson = NULL;
char* string1 = NULL;
char* string2 = NULL;
char* string3 = NULL;
int age = 0;
fd=open(FILENAME, O_RDWR);
if(fd < 0)
{
printf("open %s failure\n", FILENAME);
return -1;
}
rv=lseek(fd, 0, SEEK_CUR);
if(rv < 0)
{
printf("lseek %s failure\n", FILENAME);
return -1;
}
rv=read(fd, buf, sizeof(buf));
if(rv < 0)
{
printf("read %s failure\n", FILENAME);
return -1;
}
close(fd);
cjson=cJSON_Parse(buf);//将数据转换成cjson格式,记得使用cJSON_Delete(cJSON *c), 防止内存泄漏
if(cjson == NULL)//判断是否成功
{
printf("data pack to cjson failure\n");
return -1;
}
printf("%s\r\n",cJSON_Print(cjson));//打印输出cjsong格式
printJson(cjson);
cJSON_Delete(cjson);//释放内存, 一般都是在最后释放,一定要等上面的打印完,因为这些指针指向这些内存
return 0;
}
运用上面第一个函数我们可以解决json数据解析的格式,当然这个函数适用于复杂的简单的所有json数据格式都能够解析。