cJSON库 API解析(下)
写在前面:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
继上一篇 cJSON库 API解析(上),本篇为下篇,以解析 JSON数据包为主
一、JSON数据解析
在 cJSON里,解析 JSON数据包,其实就是通过搜寻对应的配对关键符号或者关键字,然后一个一个剥离成为链表节点(键值对)的过程。
其所支持的解析函数有以下几个:
-
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
-
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
-
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
-
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
但一般来说,平常我们只需要用到 cJSON_Parse();函数来解析。同样的,在调用了 parse函数后,使用完毕需要调用 cJSON_Delete();及时释放;
整个解析过程,其核心操作函数为:
/* Parser core - when encountering text, process appropriately. */
static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer)
{
if ((input_buffer == NULL) || (input_buffer->content == NULL))
{
return false; /* no input */
}
/* parse the different types of values */
/* null */
if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0))
{
item->type = cJSON_NULL;
input_buffer->offset += 4;
return true;
}
/* false */
if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0))
{
item->type = cJSON_False;
input_buffer->offset += 5;
return true;
}
/* true */
if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0))
{
item->type = cJSON_True;
item->valueint = 1;
input_buffer->offset += 4;
return true;
}
/* string */
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"'))
{
return parse_string(item, input_buffer);
}
/* number */
if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9'))))
{
return parse_number(item, input_buffer);
}
/* array */
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '['))
{
return parse_array(item, input_buffer);
}
/* object */
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{'))
{
return parse_object(item, input_buffer);
}
return false;
}
就像前面说的,通过搜寻对应的配对关键符号或者关键字去调用不同的处理函数,然后配对校验,并把相应的数据插入到根结点,形成一个个相连的子节点链表。
二、JSON数据获取
当调用完上面的解析函数后,返回的是根结点指针,通过这个 cJSON的结构指针,我们就可以利用其解析后每个节点所对应的类型,快速寻找同类型的数据,再根据提供的键(名称)来获取数据。
下面就来认识一下常用的 API函数:
1、类型校验:
-
False:
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
-
True:
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
-
布尔值:
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
-
null:
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
-
数值:
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
-
字符串:
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
-
数组:
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
-
对象:
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
不难发现,这些函数都是用于判断参数的类型的,因此返回值只有 true(真)和 false(假);用的比较多的是 cJSON_IsFalse();和 cJSON_IsTrue();,直接判断 JSON数据包里的布尔变量。
2、信息提取:
-
数组:
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
-
对象:
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
-
对象(名称区分大小写):
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
3、校验类型并返回值:
-
字符串:
CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);
-
数值:
CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item);
4、获取项目数:
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
5、错误分析:
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
三、示例
以上篇打印的封装的数据信息为例;
原封装的 JSON数据包:
{
"name": "cJSON",
"version": "v1.7.14",
"file": {
"name": "cJSON.c",
"size": 75.8,
"unit": "KB"
},
"released": [
2020,
"Sep",
3
],
"latest": true
}
随后解析打印出来的信息:
name:cJSON
version:v1.7.14
file:cJSON.c
size:75.800000
unit:KB
released date:2020 Sep 3
Is it necessary to update?
not update
代码执行(沿用上篇的封装代码):
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "cJSON.h"
int main(int argc, char *argv[])
{
cJSON *jtest = NULL;
cJSON *jfile = NULL;
cJSON *jissue = NULL;
cJSON *jyear = NULL;
cJSON *jmonth = NULL;
cJSON *jday = NULL;
char *str = NULL;
/* 创建一个 JSON格式的主对象(主链表头结点) */
jtest = cJSON_CreateObject();
/* 追加字符串类型的 JSON数据到主对象中(添加一个链表节点) */
cJSON_AddStringToObject(jtest, "name", "cJSON");
cJSON_AddStringToObject(jtest, "version", "v1.7.14");
/* 追加一个对象到主对象中(添加一个链表节点) */
jfile = cJSON_AddObjectToObject(jtest, "file");
/* 往追加的对象添加对应的值 */
cJSON_AddStringToObject(jfile, "name", "cJSON.c");
cJSON_AddNumberToObject(jfile, "size", 75.8);
cJSON_AddStringToObject(jfile, "unit", "KB");
/* 创建一个 JSON格式的数组(另一个链表头结点) */
jissue = cJSON_CreateArray();
/* 创建相应的值并把这些值添加到数组里 */
jyear = cJSON_CreateNumber(2020);
cJSON_AddItemToArray(jissue, jyear);
jmonth = cJSON_CreateString("Sep");
cJSON_AddItemToArray(jissue, jmonth);
jday = cJSON_CreateNumber(3);
cJSON_AddItemToArray(jissue, jday);
/* 把已经填好的数据的数组插入到主对象中 */
cJSON_AddItemToObject(jtest, "released", jissue);
/* 追加一个值为 True的布尔类型的 JSON数据到主对象中(添加一个链表节点) */
cJSON_AddTrueToObject(jtest, "latest");
/* 打印 JSON对象(整条链表)的所有数据 */
str = cJSON_Print(jtest);
printf("%s\n\n", str);
/* 释放整条链表的内存数据 */
cJSON_Delete(jtest);
/* ------------------------- 以上为上篇的封装代码 ------------------------- */
/* ------------------------- 数据保留并初始化变量 ------------------------- */
jtest = NULL;
jfile = NULL;
jissue = NULL;
jyear = NULL;
jmonth = NULL;
jday = NULL;
cJSON *jtemp = NULL;
/* ------------------------- 以下为本篇的解析代码 ------------------------- */
/* 解析整段 JSON数据 */
jtest = cJSON_Parse(str);
if (jtest == NULL)
{
printf("parse fail.\n");
return -1;
}
/* 依次根据名称提取 JSON数据(键值对) */
jtemp = cJSON_GetObjectItem(jtest, "name");
printf("name:%s\n", jtemp->valuestring);
jtemp = cJSON_GetObjectItem(jtest, "version");
printf("version:%s\n\n", jtemp->valuestring);
/* 解析嵌套的 JSON对象 */
jfile = cJSON_GetObjectItem(jtest, "file");
jtemp = cJSON_GetObjectItem(jfile, "name");
printf("file:%s\n", jtemp->valuestring);
jtemp = cJSON_GetObjectItem(jfile, "size");
printf("size:%f\n", jtemp->valuedouble);
jtemp = cJSON_GetObjectItem(jfile, "unit");
printf("unit:%s\n\n", jtemp->valuestring);
/* 解析嵌套的 JSON数组 */
jissue = cJSON_GetObjectItem(jtest, "released");
jyear = cJSON_GetArrayItem(jissue, 0);
jmonth = cJSON_GetArrayItem(jissue, 1);
jday = cJSON_GetArrayItem(jissue, 2);
printf("released date:%d ", jyear->valueint);
printf("%s ", jmonth->valuestring);
printf("%d\n\n", jday->valueint);
/* 解析布尔型数据 */
printf("Is it necessary to update?\n");
jtemp = cJSON_GetObjectItem(jtest, "latest");
cJSON_IsTrue(jtemp) ? printf("not update") : printf("update");
/* 等同于 cJSON_IsFalse(jtemp) ? printf("update") : printf("not update"); */
printf("\n\r");
cJSON_Delete(jtest);
cJSON_free(str);
return 0;
}
实例:
四、内存管理
1、cJSON_Delete();函数
/* Delete a cJSON structure. */
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item)
{
cJSON *next = NULL;
while (item != NULL)
{
next = item->next;
if (!(item->type & cJSON_IsReference) && (item->child != NULL))
{
cJSON_Delete(item->child);
}
if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL))
{
global_hooks.deallocate(item->valuestring);
}
if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))
{
global_hooks.deallocate(item->string);
}
global_hooks.deallocate(item);
item = next;
}
}
通过上面的代码可以了解到,当调用 cJSON_Delete();函数后,会通过 while循环一直从当前节点删除释放其后面的节点,直至到尾部结点 null节点为止;因此,在应用中,一般都是传入主链表的头结点来释放整个 JSON数据包。
2、cJSON_Hooks里的钩子函数
在 cJSON项目里面,是留有 cJSON_InitHooks();外部引用内存管理函数的 API接口的,其原型:
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
通过结构体 struct cJSON_Hooks跟内部调用的内存分配挂钩,其 Hooks原型:
typedef struct cJSON_Hooks
{
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
void *(CJSON_CDECL *malloc_fn)(size_t sz);
void (CJSON_CDECL *free_fn)(void *ptr);
} cJSON_Hooks;
一般情况是默认不调用 cJSON_InitHooks();函数的,因此,其内存分配管理处于默认状态,使用的是以下标准内存分配函数:
#if defined(_MSC_VER)
/* work around MSVC error C2322: '...' address of dllimport '...' is not static */
static void * CJSON_CDECL internal_malloc(size_t size)
{
return malloc(size);
}
static void CJSON_CDECL internal_free(void *pointer)
{
free(pointer);
}
static void * CJSON_CDECL internal_realloc(void *pointer, size_t size)
{
return realloc(pointer, size);
}
#else
#define internal_malloc malloc
#define internal_free free
#define internal_realloc realloc
#endif
如此一来,假设我们在系统上跑了 FreeRTOS(或者其他 RTOS),那么,在默认情况下,如果使用其标准内存分配函数,这样,对于多线程来讲是不安全的,所以,可以利用该函数重新把内存分配函数定义调用;例如在 FreeRTOS中:
cJSON_Hooks cJSON_mem;
cJSON_mem.malloc_fn = pvPortMalloc;
cJSON_mem.free_fn = vPortFree;
cJSON_InitHooks(&cJSON_mem);
通过该钩子函数,把 cJSON内部调用的内存分配处理,更换为线程安全的 pvPortMalloc();和 vPortFree();函数。
3、cJSON_malloc();和 cJSON_free();
其原型分别为:
CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
CJSON_PUBLIC(void) cJSON_free(void *object);
一般来说,cJSON_malloc();很少用,因为 cJSON的数据处理 API函数都默认会自动分配内存;而 cJSON_free();则更多的是用来 free cJSON格式化出来的数据(即调用 print类的 API接口)。
五、扩展
物色到另外一个比较详细 cJSON分析的文章:
https://www.cnblogs.com/qinjinyu/p/14086938.html
更多推荐
所有评论(0)