写在前面:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。


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

GitHub 加速计划 / cj / cJSON
10.45 K
3.16 K
下载
Ultralightweight JSON parser in ANSI C
最近提交(Master分支:2 个月前 )
424ce4ce This reverts commit 5b502cdbfb21fbe5f6cf9ffbd2b96e4281a741e6. Related to #860 4 个月前
32497300 - 5 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐