cJSON 项目可以说是一个很适合学习及应用 C 语言中的链表的项目,刨析它的源码,你会惊叹它设计之巧妙,其代码为之简洁;同时,在嵌入式应用场景中也经常发现它的身影。

本系列分为上下两篇,其中上篇以应用分析其 JSON 数据封装为主。

# 介绍

在认识 cJSON 之前,先来认识一下 JSON:

JSONJavaScript Object Notation,JavaScript 对象表示法,读作 /ˈdʒeɪsən/)是一种由道格拉斯・克罗克福特构想和设计、轻量级的资料交换语言,该语言以易于让人阅读的文字为基础,用来传输由属性值或者序列性的值组成的数据对象。JSON 采用完全独立于语言的文本格式,但是也使用了类似于 C 语言家族的习惯(包括 C, C++, C#, Java, JavaScript, Perl, Python 等)。 这些特性使 JSON 成为理想的数据交换语言。

其官网:https://www.json.org/json-en.html

而 cJSON,顾名思义就是一个使用 C 语言编写的 JSON 数据解析器,目前 cJSON 项目托管在 Github 上,仓库地址如下:https://github.com/DaveGamble/cJSON;以前的旧版本则是存放在:https://sourceforge.net/projects/cjson/files/ 上面,但现已停止更新,继而转到 Github 上了。

# JSON 语法

JSON 的基本数据类型:

  1. 对象(object):若干无序的 “键值对” (key-value pairs),其中键是数值或字符串,以花括号 { 开始,并以 } 结束。

  2. 数组 / 值的有序列表(array):有序的零个或者多个值,使用方括号 [ ] 括起来。

  3. 字符串(string):以双引号 " " 括起来的零个或多个 Unicode 码位。支持反斜杠开始的转义字符序列。

  4. 数值(number):不区分整数与浮点数。JavaScript 用双精度浮点数 double 表示所有数值。

  5. 布尔值(boolean):表示为 true 或者 false

  6. null 类型:值写为 null

1、JSON 对象是一个若干无序的 "名称 / 值" 键值对的集合:

  • 以 " { "开始,以" } " 结束,允许嵌套使用;
  • 每个名称和值成对出现,名称和值之间使用 " : " 分隔;
  • 键值对之间用 " , " 分隔
  • 在这些字符前后允许存在无意义的空白符;

2、JSON 数组是一个有序的零个或者多个值的序列表:

  • 以 " [ "开始,以" ] " 结束,允许嵌套使用;
  • 每个值可以为任意类型,可以是双引号括起来的字符串(string)、数值(number)、 truefalsenull 、对象(object)或者数组(array);
  • 元素之间用 " , " 分隔
  • 在这些元素前后允许存在无意义的空白符;

# cJSON 结构

在下载的 cJSON 源码中,实际用到的文件只有两个(cJSON.c 和 cJSON.h),因此 cJSON 具有超轻便,可移植,单文件的特点。

对于 cJSON 文件,整个的数据结构就主要用到以下结构体:

/* The cJSON structure: */
typedef struct cJSON
{
    /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
    struct cJSON *next;
    struct cJSON *prev;
    /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
    struct cJSON *child;
    /* The type of the item, as above. */
    int type;
    /* The item's string, if type==cJSON_String  and type == cJSON_Raw */
    char *valuestring;
    /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
    int valueint;
    /* The item's number, if type==cJSON_Number */
    double valuedouble;
    /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
    char *string;
} cJSON;

从上面的代码可以看出,cJSON 的设计思想是 --- 链表。

然后我们分析一下各部分的成员信息:

  • type :用于表示该键值对中值的类型;
  • valuestring :如果键值类型 (type) 是字符串,则将该指针指向键值;
  • valueint :如果键值类型 (type) 是整数,则将该指针指向键值;
  • valuedouble :如果键值类型 (type) 是浮点数,则将该指针指向键值;
  • String :用于表示当前键值对的名称;

还有两个 cJSON 格式的结构体指针:

  • next :指向下一个键值对
  • prev :指向上一个键值对

最后一个 cJSON 格式的结构体指针:

  • child :该子指针指向当前数组 / 对象中的节点

如果是想要分析 cJSON 它的代码设计,除开那两个承接上下节点的 nextprev 结构体指针,那么你还要重点关注上面说到 type 成员,它支持以下定义:

The type can be one of the following:

  • cJSON_Invalid (check with cJSON_IsInvalid ): Represents an invalid item that doesn't contain any value. You automatically have this type if you set the item to all zero bytes.
  • cJSON_False (check with cJSON_IsFalse ): Represents a false boolean value. You can also check for boolean values in general with cJSON_IsBool .
  • cJSON_True (check with cJSON_IsTrue ): Represents a true boolean value. You can also check for boolean values in general with cJSON_IsBool .
  • cJSON_NULL (check with cJSON_IsNull ): Represents a null value.
  • cJSON_Number (check with cJSON_IsNumber ): Represents a number value. The value is stored as a double in valuedouble and also in valueint . If the number is outside of the range of an integer, INT_MAX or INT_MIN are used for valueint .
  • cJSON_String (check with cJSON_IsString ): Represents a string value. It is stored in the form of a zero terminated string in valuestring .
  • cJSON_Array (check with cJSON_IsArray ): Represent an array value. This is implemented by pointing child to a linked list of cJSON items that represent the values in the array. The elements are linked together using next and prev , where the first element has prev.next == NULL and the last element next == NULL .
  • cJSON_Object (check with cJSON_IsObject ): Represents an object value. Objects are stored same way as an array, the only difference is that the items in the object store their keys in string .
  • cJSON_Raw (check with cJSON_IsRaw ): Represents any kind of JSON that is stored as a zero terminated array of characters in valuestring . This can be used, for example, to avoid printing the same static JSON over and over again to save performance. cJSON will never create this type when parsing. Also note that cJSON doesn't check if it is valid JSON.

Additionally there are the following two flags:

  • cJSON_IsReference : Specifies that the item that child points to and/or valuestring is not owned by this item, it is only a reference. So cJSON_Delete and other functions will only deallocate this item, not its child / valuestring .
  • cJSON_StringIsConst : This means that string points to a constant string. This means that cJSON_Delete and other functions will not try to deallocate string .

<br/>

# JSON 数据封装

封装一个 JSON 格式的数据包,其实就是创建链表和向链表中添加节点的过程。

在 cJSON 源码里面是存放着很多 API 接口的,但是,一般来说我们并不是全部用到,而且有些函数是辅助函数,例如:

cJSON *Info;
Info = cJSON_CreateObject();
cJSON_AddStringToObject(Info, "Nationality", "China");
/* 等价于 */
cJSON *Info, *jtext;
Info = cJSON_CreateObject();
jtext = cJSON_CreateString("China");
cJSON_AddItemToObject(Info, "Nationality", jtext);

然后下面就分析一些常用的 API 函数:

1、创建原始框架:

  • 数组(等于创建了一个空的 [ ] ): CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
  • 对象(等于创建了一个空的 { } ): CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);

2、追加类型值:

  • 数组: CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);

  • 对象: CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);

3、追加对应的值到对象中:

  • null: CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
  • True: CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
  • False: CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
  • 布尔值(实际为 True 和 False 合并): CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
  • 数值: CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
  • 字符串: CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
  • 对象: CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
  • 数组: CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);

4、创建同一类型的值到数组中:

  • 整形: CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
  • 单精度: CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
  • 双精度: CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
  • 字符串: CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);

5、创建对应类型的值

  • null: CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
  • True: CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
  • False: CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
  • 布尔值: CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
  • 数值: CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
  • 字符串: CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);

# 示例

{
  "name": "cJSON",
  "version": "v1.7.14",
  "file": {
    "name": "cJSON.c",
    "size": 75.8,
    "unit": "KB"
  },
  "released": [
    2020,
    "Sep",
    3
  ],
  "latest": true
}

以打印输出上面为例,建立以下代码:

#include <stdio.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", str);
    cJSON_free(str);
    cJSON_Delete(jtest);
    return 0;
}

实例:

image-20210130174615600

# 注意事项

1、在调用了 create 接口的数据,并且用完一个完整的 JSON 格式包后,必须要使用 cJSON_Delete (); 释放内存,且不说不释放会造成泄露数据,在嵌入式中,内存容量可是很少的,很容易就耗完内存。

2、cJSON_Delete (); 函数,并不是每调用一个 create 接口,等用完都要一一对应释放,而是要释放主链表,简单的来说要处理的是头一个创建的数据类型,对于后期往其追加的数据,cJSON_Delete (); 函数会自动把插入进来的节点删除掉,这个在下篇再详细分析。

3、cJSON 里面的 Print 接口格式化出来返回的数据,需要调用 cJSON_free (); 释放。cJSON 的内存申请涉及到初始化钩子函数 cJSON_InitHooks (); 那里,这个也是下篇分析。

更新于 阅读次数

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

夏沫の浅雨 微信支付

微信支付

夏沫の浅雨 支付宝

支付宝