目录
背景知识
JSON数据结构
cJSON重要接口函数
解析案例
移植注意 事项
JSON是一种轻量级的数据交换格式,这里不做详细的分析,简单的理解为,是互联网上的一种数据打包协议,比较方便人阅读和编写,下面是阿里云物联网设备影子信息的json格式,如下所示:
{
"state": {
"reported": {
"hz": 20,
"temp_comp": -0.5
},
"desired": {
"hz": 5,
"temp_comp": 10
}
},
"metadata": {
"reported": {
"hz": {
"timestamp": 1559705221
},
"temp_comp": {
"timestamp": 1559705221
}
},
"desired": {
"hz": {
"timestamp": 1560143969
},
"temp_comp": {
"timestamp": 1560143969
}
}
},
"timestamp": 1560143969,
"version": 0
}
如果仔细看,其实各个数据关系还是比较明显的。在json中,一切都是对象(object),因此任何支持的数据类型,都是可以通过JSON来表示的,例如字符串、数字、对象、数组。JSON本质是一个字符串,而json的对象则是其中的元素:
var obj = {a:'hello', b: 'world'}; //这是一个对象,注意键名也是通过引号包裹
var json = '{"a": "hello", "b":"world"}'; //这是一个JSON字符串,本质上是一个字符串
再 专业的json知识点,这里就先不讲了,毕竟本文的主要目的是讲述cJSON。
从上面的分析,我们可以知道,对于json的解析,简单的讲就是从 一堆字符串中,筛选出自己需要的信息。这个听起来很简单,真正实现起来确不是那么简单,因为字符串筛选功能本身就很难,再 加上其中的对象排列顺序可能不同,中间有没有空格,数值是 整数、还是浮点数,是正数,还是负数,这些都是很麻烦的,如果我们的数据格式是固定的还好,但是稍微有一点变动,就会很麻烦,所以写出一个兼容性很强的json解析程序, 还是比较难的,而cJSON就是一个专门用来解析JSON字符串的,因为简洁又简单,效率还快,移植也特别方便,只需要一个cJSON.c和cJSON.h文件。
我们先说一下json常用的概念。
/* The cJSON structure: */
typedef struct cJSON
{
struct cJSON *next;
struct cJSON *prev; //双向链表指针,用于遍历数组或对象链的向前、向后的指针
struct cJSON *child; //数组或子对象节点
int type; //key 的类型(要解析的目标string类型)
char *valuestring; //字符串值
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
int valueint; //整数值
double valuedouble; //浮点数值
char *string; //要解析的目标string
} cJSON;
说明:
1. cJSON数据结构,是采用双向链表来存储数据的,访问方式像一颗树,每一个节点都可以有兄弟节点, 通过next/prev指针来查找, 每个节点也可以有子节点,通过child指针来访问,进入下一层,只有节点是对象或者数组时,才可以有子 节点。这其实是表示,json格式可以包裹多层,如果我们要查找的数据在最里层,就需要一层一层的查找,比如上面我们举例说的阿里云物联网影子信息,最外层是整个json对象, 我们可以认为是“根”,这个“根”有多个子节点:state、metadata、timestamp、version。
子节点state又包含reported和desired子节点,而desired节点又有 hz和temp_comp子节点,所以我们遍历的时候,可以一层一层的剥开分析。后面的代码有详细的分析。
2、type是 要解析的目标string的 类型,类型具体种类如下:
cJSON_Invalid
cJSON_False
cJSON_True
cJSON_NULL
cJSON_Number
cJSON_String
cJSON_Array
cJSON_Object
cJSON_Raw
具体详细介绍,参考官方手册里的介绍,简单的讲,如果是Number类型,则valueint或valuedouble中存着对应值。若期望是int,则访问valueint,若期望是float,则访问valuedouble。
若是String类型,则valueString中存放着值。
cJSON *cJSON_Parse(const char *value);
功能:用于解析JSON数据包,按照cJSON结构体的机构序列化整个数据包,其实这是使用cJSON解析功能的第一条指令,我们需要将一个字符串作为参数传递给该函数,也就是value,然后该函数会是使用malloc申请一块内存区域,大小为cJSON结构体,存放该字符串,后面所有的操作,都是根据这条命令的返回作为“根”对象。
参数:*value——要解析的字符串。
返回: cJSON结构体指针,指向我们 解析的字符串,所以我们需要对该返回进行判断,如果是NULL,则失败。
cJSON *cJSON_GetObjectItem(cJSON *object, const char *string);
功能:获取json指定对象成员,string就是我们 想要筛选的对象名。
参数: object:cJSON对象,既可以是cJSON_Parse返回的“根”对象,也可以是子对象。
string:要获取的指定对象名称。
返回值:要获取的指定对象句柄。当然类型是cJSON指针,就是前面提到的child指针。
void cJSON_Delete(cJSON *c);
用来释放cJSON_Parse函数获取的句柄,释放整个内存,用在解析完成后调用。
特别注意:cJSON的解析会伴随这malloc申请内存,我们在解析完成后,务必进行delete, 否则会造成内存溢出,程序死机。
下面我们就对前面举例的json字符串进行接下,代码如下:
#include
#include
#include
int main(int argc, char *argv[]) {
const char *line = "{\"state\": {\"desired\": {\"hz\": 5,\"temp_comp\": -0.1}}, \
\"metadata\": {\"reported\": {\"hz\": {\"timestamp\": 1559720433}, \
\"temp_comp\": {\"timestamp\": 1559720433}}, \
\"desired\": {\"hz\": {\"timestamp\": 1559720433},\"temp_comp\": {\"timestamp\": 1559720433}}}, \
\"timestamp\": 1559720433, \
\"version\": 3}";
cJSON *json;
//char *out;
json = cJSON_Parse( line ); //
if(json == NULL)
printf("json fmt error:%s\n.", cJSON_GetErrorPtr());
else{
cJSON *object = cJSON_GetObjectItem(json, "state");
cJSON *object1 = cJSON_GetObjectItem(object, "desired");
cJSON *item = cJSON_GetObjectItem(object1, "hz");
printf("desired->hz: %d\n", item->valueint);
item = cJSON_GetObjectItem(object1, "temp_comp");
printf("desired->temp_comp: %f\n", item->valuedouble);
cJSON_Delete(json);
}
return 0;
}
程序运行结果:
从上面的代码可以验证前面说的,每个cJSON数据结构中,又有了child字节点,我们可以逐层进行剥开,来获取我们想要的数据。
cJSON是非常轻量级的,我们只需要将cJSON.c和cJSON.h加入到我们的工程中即可,然后包含cJSON.h就可以了。唯一需要注意的就是,由于cJSON会频繁的调用malloc,也就是会申请内存,所以我们的程序中,有2方面 需要注意:
(1)解析完成后,一定要调用cJSON_Delete释放掉cJSON_Parse生成的句柄,也就是释放内存。
(2)在keil或者IAR中,设置“堆Heap”的空间尽量大些, 参考网上的推荐,设置为4096。其实如果只是筛选,只要我们及时delete,不修改也行。
在IAR中设置Heap的方式是,Project->options->linker->config->Override default->Edit->Stack/Heap Sizes
在Keil中,应该是在startup.s文件中,找对应Heap的 定义字段修改即可。
小结:上面的分析只是其中一种JSON格式,JSON格式还有其他的类型,cJSON都有具体的API接口函数,这里这里就不赘述了,还是看官方文档吧。