继上一篇 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;
}

实例:

image-20210131224342050

# 内存管理

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 接口)。

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

夏沫の浅雨 微信支付

微信支付

夏沫の浅雨 支付宝

支付宝