最近在做数据上三大云平台的项目,用的是MQTT协议,阿里云、腾讯云、华为云那边解析数据用的是json格式,下发的也是json格式,所以特地学一下json的封装和解析过程,以及其API的使用,作了以下笔记。
一、概述
cJSON是一个仅有一个.h文件,一个.c文件组成的JSON解析器,它是由纯C(ANSI C89)实现的,跨平台性较好。cJSON是采用链表存储的。
cJSON库在使用的时候只需要如下两步:将cJSON.c(或者库文件)和cJSON.h添加到项目中即可;如果在命令行中进行链接还需要加上-lm表示链接math库。
二、下载安装
git clone https://github.com/DaveGamble/cJSON.git
使用的时候找到cJSON.c和cJSON.h 文件将其添加到项目工程中,然后包含头文件cJSON.h即可。
二、基本语法
(1)JSON表示方法
以"{“开始,以”}"结束 数据在键/值对中; 数据由逗号分开; 花括号保存对象; 方括号保存数组;
(2)JSON的三种语法
键/值对key:value,中间用冒号分开,键要加双引号,但值不一定,例如"name":“zcx”。 对象放在花括号中,可用包含多个键值对,例如{“name”:“zcx”,“address”:“深圳”}。 数组JSON数组放在方括号中,数组成员可以是对象、值、也可以是数组,例如[“小明”,“二狗”,“翠花”,{“adress”:“北京”}]。
(3)JSON的值
可以是:数字(整数或浮点数)、字符串(在双引号中)、布尔值(true或false)、数组(在方括号中)、一个json对象(在花括号中)、null。
(4)JSON两种结构
**对象:**对象在js中表示为“{}”括起来的内容,数据结构为{key:value,key:value,…}的键值对的结构,在面向对象的语言中,key为对象的属性,value为对应的属性值,所以很容易理解,取值方法为 对象.key 获取属性值.
**数组:**数组在js中是中括号“[]”括起来的内容,数据结构为[“java”,“javascript”,“vb”,…],取值方式和所有语言中一样,使用索引获取。
经过对象、数组2种结构就可以组合成复杂的数据结构了。
三、cJSON数据结构
cJSON使用cJSON结构体来表示一个JSON数据,定义在cJSON.h中,源码如下:
typedef struct cJSON
{
struct cJSON *next;
struct cJSON *prev;
struct cJSON *child;
int type;
char *valuestring;
int valueint;
double valuedouble;
char *string;
} cJSON;
cJSON将其中一条json数据(键值对)抽象出来,用上面的结构体表示。但是往往一段完整的json数据不只一个数据,而且还需要进行增删改查等操作,所以其使用了链表的形式进行存放json数据,json中,数组也表示一个对象,用链表存储。
四、cJSON封装过程
封装JSON数据的过程,其实就是创建链表和向链表中添加节点的过程。熟悉一下单链表,以及一些术语:
头指针:指向链表头结点的指针; 头结点:不存放有效数据,方便链表操作; 首节点:第一个存放有效数据的节点; 尾节点:最后一个存放有效数据的节点;
不过cJSON用的可不是单链表存储数据,而是双亲孩子兄弟表示法,因为其一个节点数据结构里面包含了三个指针,分别是next、prev、child。
明白了这几个概念,我们就可以知道一个cJSON数据是如何创建的。
(1)创建头指针:
cJSON* cjson_test = NULL;
(2)创建头节点,并将头指针指向头节点:
cjson_test = cJSON_CreateObject();
(3)向链表添加节点:
cJSON *cJSON_CreateArray();
cJSON_AddNullToObject(cJSON * const object, const char * const name);
cJSON_AddTrueToObject(cJSON * const object, const char * const name);
cJSON_AddFalseToObject(cJSON * const object, const char * const name);
cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
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);
cJSON *cJSON_CreateString(const char *string);
void cJSON_AddItemToArray(cJSON *array, cJSON *item);
void cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
void cJSON_Delete(cJSON *c)
(4)输出cJSON数据
一段完整的JSON数据就是一条长长的链表,cJSON提供了一个API,可以将整条链表中存放的JSON信息输出到一个字符串中:
(char *) cJSON_Print(const cJSON *item);
使用的时候,只需要定义一个指针,然后让这个指针指向这个函数返回的地址即可。
(5)使用实例
#include <stdio.h>
#include "cJSON.h"
int main()
{
cJSON *temp_item = cJSON_CreateObject();
cJSON_AddNumberToObject(temp_item, "Temperature", 33.21);
cJSON *ack_item = cJSON_CreateObject();
cJSON_AddNumberToObject(ack_item, "ack", 0);
cJSON*root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "method","thing.service.property.post");
cJSON_AddStringToObject(root, "id", "device");
cJSON_AddItemToObject(root, "sys",ack_item);
cJSON_AddItemToObject(root, "params",temp_item);
cJSON_AddStringToObject(root, "version","1.0.0");
char *str = cJSON_Print(root);
printf("%s\n", str);
return 0;
}
运行结果:
五、cJSON数据解析
解析JSON数据的过程,其实就是将整个cJSON数据包剥离一个一个链表节点(键值对)的过程。 四个常用的cJSON解析函数:
(1)cJSON_Parse
cJSON *cJSON_Parse(const char *value);
**作用:**将一个JSON数据包,按照cJSON结构体的结构序列化整个数据包,并在堆中开辟一块内存存储cJSON结构体
**返回值:**成功返回一个指向内存块中的cJSON的指针,失败返回NULL
(2)cJSON_GetObjectItem
cJSON *cJSON_GetObjectItem(cJSON *object,const char *string);
**作用:**获取JSON字符串字段值
**返回值:**成功返回一个指向cJSON类型的结构体指针,失败返回NULL
(3)cJSON_Delete
void cJSON_Delete(cJSON *c);
作用:释放位于堆中cJSON结构体内存 返回值:无
#include <stdio.h>
#include "cJSON.h"
int main()
{
char * str = "{\
\"method\": \"thing.service.property.post\",\
\"id\": \"device\",\
\"sys\": {\
\"ack\": 0\
},\
\"params\": {\
\"Temperature\": 33.21,\
\"datime\":\"2022.5.4\"\
},\
\"version\": \"1.0.0\"\
}";
cJSON *obj = cJSON_Parse(str);
cJSON *version_obj = cJSON_GetObjectItem(obj, "version");
printf("version:%s\n", version_obj->valuestring);
cJSON *params_obj = cJSON_GetObjectItem(obj, "params");
printf("Temperature:%f\n", params_obj->child->valuedouble);
printf("datime:%s\n", params_obj->child->next->valuestring);
return 0;
}
运行结果:
注意事项
在进行json数据解析时,如果不知道你取的该节点的值是什么类型时,可以用该节点中type来判断,然后再用valuedouble,valuestring或valueint获取该节点的值。
内存问题: cJSON的所有操作都是基于链表的,所以cJSON在使用过程中大量的使用malloc从堆中分配动态内存的,所以在使用完之后,应当及时调用下面的函数,清空cJSON指针所指向的内存,该函数也可用于删除某一条数据:
(void) cJSON_Delete(cJSON *item);
|