前言
最近在研究物联网设备的数据传输,就想到了用JSON来进行,毕竟可以让设备数据对象化,同时各语言之间通常也有对于的解析器或库,来进行数据的获取。比如本文的介绍的cJSON就是作用于C语言上。文章主要内容,分析官方案例所用到的函数作用,来学习理解或快速回忆使用方法。
背景
- 拉取cJSON-GitHub主页项目,并且将
cJSON.h
、cJSON.c
放到项目即可。(当然cJSON也提供几种针对不同环境的构建方法,暂时用不到就不赘述) - 若不熟悉JSON,可以参考JSON 教程 | 菜鸟教程
案例
下面的构成和解析案例都是来自官方,功能函数的注释都是笔者自己的理解,如果和官方注解有冲突,请以官方的为准。还有只例出跟案例相关的函数,其余更多的函数可以自行阅读cJSON库文件。
案例所用的JSON结构:
{
"name": "Awesome 4K",
"resolutions": [
{
"width": 1280,
"height": 720
},
{
"width": 1920,
"height": 1080
},
{
"width": 3840,
"height": 2160
}
]
}
构成案例
基于官方构成JSON对象的案例来分析,使用到的函数作用。
代码案例1
让我们构建上面的JSON并将其打印为字符串:
//创建具有支持分辨率列表的监视器(笔者:这里并不是cJSON存在这个概念结构,而是该创建cJSON对象的数据意义)
//注意:返回一个堆分配的字符串,使用后需要释放它。
char *create_monitor(void)
{
const unsigned int resolution_numbers[3][2] = {
{1280, 720},
{1920, 1080},
{3840, 2160}
};
char *string = NULL;
cJSON *name = NULL;
cJSON *resolutions = NULL;
cJSON *resolution = NULL;
cJSON *width = NULL;
cJSON *height = NULL;
size_t index = 0;
cJSON *monitor = cJSON_CreateObject();
if (monitor == NULL)
{
goto end;
}
name = cJSON_CreateString("Awesome 4K");
if (name == NULL)
{
goto end;
}
/* 创建成功后,立即将其添加到监视器中,从而将指针的所有权转移到它 */
cJSON_AddItemToObject(monitor, "name", name);
resolutions = cJSON_CreateArray();
if (resolutions == NULL)
{
goto end;
}
cJSON_AddItemToObject(monitor, "resolutions", resolutions);
for (index = 0; index < (sizeof(resolution_numbers) / (2 * sizeof(int))); ++index)
{
resolution = cJSON_CreateObject();
if (resolution == NULL)
{
goto end;
}
cJSON_AddItemToArray(resolutions, resolution);
width = cJSON_CreateNumber(resolution_numbers[index][0]);
if (width == NULL)
{
goto end;
}
cJSON_AddItemToObject(resolution, "width", width);
height = cJSON_CreateNumber(resolution_numbers[index][1]);
if (height == NULL)
{
goto end;
}
cJSON_AddItemToObject(resolution, "height", height);
}
string = cJSON_Print(monitor);
if (string == NULL)
{
fprintf(stderr, "Failed to print monitor.\n");
}
end:
cJSON_Delete(monitor);
return string;
}
代码案例2
另外,我们可以使用辅助函数使我们的工作更容易一些:cJSON_Add…ToObject
。简化案例1的代码流程,大概流程思路是省略掉中间item
项的声明定义过程,直接用函数一步到位。
//注意:返回一个堆分配的字符串,使用后需要释放它。
char *create_monitor_with_helpers(void)
{
const unsigned int resolution_numbers[3][2] = {
{1280, 720},
{1920, 1080},
{3840, 2160}
};
char *string = NULL;
cJSON *resolutions = NULL;
size_t index = 0;
cJSON *monitor = cJSON_CreateObject();
if (cJSON_AddStringToObject(monitor, "name", "Awesome 4K") == NULL)
{
goto end;
}
resolutions = cJSON_AddArrayToObject(monitor, "resolutions");
if (resolutions == NULL)
{
goto end;
}
for (index = 0; index < (sizeof(resolution_numbers) / (2 * sizeof(int))); ++index)
{
cJSON *resolution = cJSON_CreateObject();
if (cJSON_AddNumberToObject(resolution, "width", resolution_numbers[index][0]) == NULL)
{
goto end;
}
if (cJSON_AddNumberToObject(resolution, "height", resolution_numbers[index][1]) == NULL)
{
goto end;
}
cJSON_AddItemToArray(resolutions, resolution);
}
string = cJSON_Print(monitor);
if (string == NULL)
{
fprintf(stderr, "Failed to print monitor.\n");
}
end:
cJSON_Delete(monitor);
return string;
}
构成案例函数
变量修饰符全部应该是CJSON_PUBLIC(cJSON *)
,还有个宏控制,下述就简化为(cJSON *)
。还有下述描述函数用伪函数原形,也就是形参部分不写类型,而是写出应该输入的数据,如果觉得看得不舒服也可以发消息给我修改。还有下述中,对象
代指json的1个层级,而不是整体。项
的概念在函数中也可以知道,就是官方用来做健值的预定义用到,当然案例2中就是直接添加的函数,越过了项。
/***** 构成案例1 *****/
/*** 创建 ***/
(cJSON *) cJSON_CreateObject(); // 创建对象(1层json对象)
(cJSON *) cJSON_CreateArray(); // 创建数组
(cJSON *) cJSON_CreateString("str"); // 创建字符串,返回cJSON*对象
(cJSON *) cJSON_CreateNumber(12345); // 创建双浮点型,返回cJSON*对象
/*** 添加 ***/
(cJSON_bool) cJSON_AddItemToArray(array,item); // 添加项到数组中
(cJSON_bool) cJSON_AddItemToObject(object, "name", name); // 添加项到对象中
/***** 构成案例2 *****/
/*** 添加和添加 ***/
(cJSON*) cJSON_AddArrayToObject(object, "Array"); // 为对象添加数组键
(cJSON*) cJSON_AddStringToObject(object, "key", "str"); // 为对象添加键:健值(字符串)
(cJSON*) cJSON_AddNumberToObject(object, "key", 123); // 为对象添加键:健值(双浮点型)
/***** 共用部分 *****/
/*** 打印与销毁 ***/
(char *) cJSON_Print(object); // 列出cJSON*结构,返回到char*对象中,供正常打印
(void) cJSON_Delete(object); // 销毁cJSON*结构(不让内存泄漏)
解析案例
基于官方解析JSON对象的案例来分析,所要解析的JSON就是上述案例构成的。
代码案例1
/* 如果显示器支持全高清,返回1,否则返回0 */
int supports_full_hd(const char * const monitor)
{
const cJSON *resolution = NULL;
const cJSON *resolutions = NULL;
const cJSON *name = NULL;
int status = 0;
cJSON *monitor_json = cJSON_Parse(monitor);
if (monitor_json == NULL)
{
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr != NULL)
{
fprintf(stderr, "Error before: %s\n", error_ptr);
}
status = 0;
goto end;
}
name = cJSON_GetObjectItemCaseSensitive(monitor_json, "name");
if (cJSON_IsString(name) && (name->valuestring != NULL))
{
printf("Checking monitor \"%s\"\n", name->valuestring);
}
resolutions = cJSON_GetObjectItemCaseSensitive(monitor_json, "resolutions");
// 笔者:该函数其实是个宏,定义的for函数,用来迭代获取
cJSON_ArrayForEach(resolution, resolutions)
{
cJSON *width = cJSON_GetObjectItemCaseSensitive(resolution, "width");
cJSON *height = cJSON_GetObjectItemCaseSensitive(resolution, "height");
if (!cJSON_IsNumber(width) || !cJSON_IsNumber(height))
{
status = 0;
goto end;
}
if ((width->valuedouble == 1920) && (height->valuedouble == 1080))
{
status = 1;
goto end;
}
}
end:
cJSON_Delete(monitor_json);
return status;
}
解析案例函数
/*** 定位数据 ***/
cJSON_ArrayForEach // 该函数是宏,定义的是for()所以后续才像函数那样又接了{}
(cJSON *) cJSON_Parse(const char *value); // 提供一个JSON块(字符串指针),这将返回一个可以查询的cJSON对象
(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); // 通过对象查询键的值
/*** 获取数据 ***/
/*
if (cJSON_IsString(name) && (name->valuestring != NULL))
{
printf("Checking monitor \"%s\"\n", name->valuestring);
}
在案例中可以看到官方是这样获取数据的,通过函数判断类型,值指向是否为空,通过指针指向储存类型值获取。
不过我看官方也有直接获取函数不知道为什么没有用。
CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);
CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item);
*/
/*** 销毁 ***/
(void) cJSON_Delete(cJSON *item); // 销毁cJSON*结构(不让内存泄漏)
链接
这里有下载好的cJSON项目,怕大家暂时l连接不上Github,时间是2024/8/7下载的。
通过百度网盘分享的文件:cJSON.rar
链接:https://pan.baidu.com/s/1JHPHrCELqCuXUIjTCJ_Zog 提取码:liam