继上一篇 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 接口)。