cJSON开源项目学习(一)

操作环境:Ubuntu18.04        gcc/g++7.5.0        cmake3.10.2

第一天:

首先,要通过Git从GitHub上下载cJSON项目:

        git clone https://github.com/DaveGamble/cJSON.git

并没有按照官方说的执行make等命令,主要文件就是cJSON.c和cJSON_Utils.c,直接用test.c文件先让项目跑起来:

        gcc -g test.c cJSON.c -o test(这里的-g是为了用gdb可以对test进行调试)

(这里提一嘴,我也是后来才发现,在GitHub上的readme中,有很多的make选项,在默认情况下,并不会启用cJSON_Utils。)

./test后会输出版本信息和很多的键值对形式的数据,如下所示:

Version: 1.7.15
{
        "name": "Jack (\"Bee\") Nimble",
        "format":       {
                "type": "rect",
                "width":        1920,
                "height":       1080,
                "interlace":    false,
                "frame rate":   24
        }
}
["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]

 接下来我决定先熟悉下cJSON的数据结构:

/* The cJSON structure: */
typedef struct cJSON
{
    struct cJSON *next;
    struct cJSON *prev;
    struct cJSON *child;
    int type;
    char *valuestring;
    /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
    int valueint;
    double valuedouble;
    char *string;
} cJSON;

可以看出,cJSON采用了一个链表的结构,cJSON结构体中的`next`和`prev`字段用于构建链表结构,将多个cJSON对象链接在一起。这种链表结构可以方便的便利和访问cJSON对象的兄弟节点。通过`next`字段访问下一个cJSON对象,通过`prev`字段访问链表中的前一个cJSON对象。      并且每个对象还有子节点,也就是`child`字段。

struct cJSON *next 指向下一个cJSON 结构的指针,用于链接多个 cJSON 对象形成链表结构
struct cJSON *prev 指向前一个cJSON 结构的指针,用于链接多个 cJSON 对象形成链表结构
struct cJSON *child 指向第一个子元素的指针,处理 JSON 对象或 JSON 数组的嵌套结构
int type 表示 cJSON 对象的类型
char * valuestring 是一个指向存储 JSON 字符串值的字符数组的指针
int valueint 是一个整数类型的值,表示 JSON 数字类型的整数值
double valuedouble 是一个双精度浮点数类型的值,表示 JSON 数字类型的浮点数值
char *str 是一个指向存储 JSON 键名的字符数组的指针(键值对键名)

type类型可以是以下之一:

  • cJSON_Invalid(检查cJSON_IsInvalid):表示不包含任何值的无效项目。如果将项目设置为全零字节,则自动具有此类型。
  • cJSON_False(检查cJSON_IsFalse):代表一个false布尔值。您还可以使用 来检查一般的布尔值cJSON_IsBool
  • cJSON_True(检查cJSON_IsTrue):代表一个true布尔值。您还可以使用 来检查一般的布尔值cJSON_IsBool
  • cJSON_NULL(检查cJSON_IsNull):代表一个null值。
  • cJSON_Number(检查cJSON_IsNumber):代表一个数字值。该值存储为 double invaluedouble和 also in valueint。如果数字超出整数范围,INT_MAXINT_MIN用于valueint.
  • cJSON_String(检查cJSON_IsString):代表一个字符串值。它以零终止字符串的形式存储在valuestring.
  • cJSON_Array(检查cJSON_IsArray):表示一个数组值。这是通过指向表示数组中值的项child的链接列表来实现的。这些元素使用andcJSON链接在一起,其中第一个元素和最后一个元素。nextprevprev.next == NULLnext == NULL
  • cJSON_Object(检查cJSON_IsObject):表示一个对象值。对象的存储方式与数组相同,唯一的区别是对象中的项将其键存储在string.
  • cJSON_Raw(检查cJSON_IsRaw):表示存储为零终止字符数组的任何类型的 JSON valuestring。例如,这可用于避免一遍又一遍地打印相同的静态 JSON 以节省性能。cJSON 在解析时永远不会创建这种类型。另请注意,cJSON 不会检查它是否是有效的 JSON。
  • cJSON_IsReferencechild: 指定指向和/或valuestring不属于该项目的项目,它只是一个参考。SocJSON_Delete和其他函数只会释放这个项目,而不是它的childvaluestring
  • cJSON_StringIsConst: 这意味着string指向一个常量字符串。这意味着cJSON_Delete和其他函数不会尝试解除分配string

                                                                                (在GitHub上的说明)

接下来我们从主函数开始:

printf("Version: %s\n", cJSON_Version());    ->

CJSON_PUBLIC(const char*) cJSON_Version(void)
{
    static char version[15];
    sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH);

    return version;
}
#define CJSON_VERSION_MAJOR 1
#define CJSON_VERSION_MINOR 7
#define CJSON_VERSION_PATCH 15

cJSON的版本信息都保存在cJSON.h文件中。这里我注意到数组使用了`static`来进行修饰,作用是保证该变量在函数调用之间保持值的持久性。数组是在函数内部定义的局部变量,如果没有`static`修饰每次调用`cJSON_Version`函数都会重新创建,并在函数调用后销毁。通过`static`的修饰,数组只会在初次调用函数时创建,并且多次调用之间值保持不变。(主版本号-次版本号-修订版本号)

        而对CJSON_PUBLIC(const char*)进行追溯后发现:

CJSON_PUBLIC(const char*)    ->

#define CJSON_PUBLIC(type)   __declspec(dllexport) type CJSON_STDCALL    ->

#define CJSON_STDCALL __stdcall

#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL这用来定义导出函数的可见性和调用约定:

        __declspec(dllexport) 是一个编译器特定的关键字,在 Windows 平台上使用。它指示编译器将被修饰的函数标记为可被动态链接的库导出函数,可以在库外部访问。

        type 是一个占位符,表示宏中的类型参数,用于指定函数的返回类型。

   CJSON_STDCALL 是一个宏,指示函数使用的调用约定,也是平台和编译器特定的。它可以用于指定函数参数的传递顺序和堆栈的清理方式等。

        __stdcall 是一种调用约定,是一种函数在编译时如何处理函数参数传递、堆栈的清理等的规定方式。调用约定决定了函数调用时参数如何被传递、堆栈如何被管理以及函数调用后如何清理堆栈。__stdcall 是一种常见的调用约定,通常用于 Windows 平台上的函数调用。使用CJSON_STDCALL 宏的地方,将会使用 __stdcall 调用约定。这意味着函数的参数传递顺序和堆栈的清理方式将遵循 __stdcall 的规定。

        通过使用这个宏定义 CJSON_PUBLIC,可以为函数指定在 Windows 平台上导出并使用特定的调用约定。这样,被标记为 CJSON_PUBLIC 的函数可以在库的外部被访问和调用。

接下来我会把我认为有用的点直接注释到代码段中:

static void create_objects(void)
{
    /* declare a few. */
    cJSON *root = NULL;    //JSON根节点
    cJSON *fmt = NULL;     //格式化信息
    cJSON *img = NULL;     //图像相关信息
    cJSON *thm = NULL;     //缩略图相关信息
    cJSON *fld = NULL;     //表示字段或其他数据信息
    int i = 0;

    /* Our "days of the week" array: */
    const char *strings[7] =
    {
        "Sunday",
        "Monday",
        "Tuesday",
        "Wednesday",
        "Thursday",
        "Friday",
        "Saturday"
    };
    /* Our matrix: */
    int numbers[3][3] =
    {
        {0, -1, 0},
        {1, 0, 0},
        {0 ,0, 1}
    };
    /* Our "gallery" item: */
    int ids[4] = { 116, 943, 234, 38793 };
    /* Our array of "records": */
    struct record fields[2] =
    {
        {
            "zip",
            37.7668,
            -1.223959e+2,
            "",
            "SAN FRANCISCO",
            "CA",
            "94107",
            "US"
        },
        {
            "zip",
            37.371991,
            -1.22026e+2,
            "",
            "SUNNYVALE",
            "CA",
            "94085",
            "US"
        }
    };
    /*关键字 volatile 用于告诉编译器,该变量的值可能会在程序的其他地方被修改
    ,因此编译器在优化代码时不应该做出假设或进行优化。它用于标记变量具有不稳
    定的、易变的特性。*/
    volatile double zero = 0.0;
    /*在此之前定义了一些JSON的数据*/

    /* Here we construct some JSON standards, from the JSON site. */

    /* Our "Video" datatype: */
    root = cJSON_CreateObject();
    cJSON_AddItemToObject(root, "name", cJSON_CreateString("Jack (\"Bee\") Nimble"));
    cJSON_AddItemToObject(root, "format", fmt = cJSON_CreateObject());
    cJSON_AddStringToObject(fmt, "type", "rect");
    cJSON_AddNumberToObject(fmt, "width", 1920);
    cJSON_AddNumberToObject(fmt, "height", 1080);
    cJSON_AddFalseToObject (fmt, "interlace");
    cJSON_AddNumberToObject(fmt, "frame rate", 24);

    /* Print to text */
    if (print_preallocated(root) != 0) {
        cJSON_Delete(root);
        exit(EXIT_FAILURE);
    }
    cJSON_Delete(root);

    /* Our "days of the week" array: */
    root = cJSON_CreateStringArray(strings, 7);

    if (print_preallocated(root) != 0) {
        cJSON_Delete(root);
        exit(EXIT_FAILURE);
    }
    cJSON_Delete(root);

    /* Our matrix: */
    root = cJSON_CreateArray();
    for (i = 0; i < 3; i++)
    {
        cJSON_AddItemToArray(root, cJSON_CreateIntArray(numbers[i], 3));
    }

    /* cJSON_ReplaceItemInArray(root, 1, cJSON_CreateString("Replacement")); */

    if (print_preallocated(root) != 0) {
        cJSON_Delete(root);
        exit(EXIT_FAILURE);
    }
    cJSON_Delete(root);

    /* Our "gallery" item: */
    root = cJSON_CreateObject();
    cJSON_AddItemToObject(root, "Image", img = cJSON_CreateObject());
    cJSON_AddNumberToObject(img, "Width", 800);
    cJSON_AddNumberToObject(img, "Height", 600);
    cJSON_AddStringToObject(img, "Title", "View from 15th Floor");
    cJSON_AddItemToObject(img, "Thumbnail", thm = cJSON_CreateObject());
    cJSON_AddStringToObject(thm, "Url", "http:/*www.example.com/image/481989943");
    cJSON_AddNumberToObject(thm, "Height", 125);
    cJSON_AddStringToObject(thm, "Width", "100");
    cJSON_AddItemToObject(img, "IDs", cJSON_CreateIntArray(ids, 4));

    if (print_preallocated(root) != 0) {
        cJSON_Delete(root);
        exit(EXIT_FAILURE);
    }
    cJSON_Delete(root);

    /* Our array of "records": */
    root = cJSON_CreateArray();
    for (i = 0; i < 2; i++)
    {
        cJSON_AddItemToArray(root, fld = cJSON_CreateObject());
        cJSON_AddStringToObject(fld, "precision", fields[i].precision);
        cJSON_AddNumberToObject(fld, "Latitude", fields[i].lat);
        cJSON_AddNumberToObject(fld, "Longitude", fields[i].lon);
        cJSON_AddStringToObject(fld, "Address", fields[i].address);
        cJSON_AddStringToObject(fld, "City", fields[i].city);
        cJSON_AddStringToObject(fld, "State", fields[i].state);
        cJSON_AddStringToObject(fld, "Zip", fields[i].zip);
        cJSON_AddStringToObject(fld, "Country", fields[i].country);
    }

    /* cJSON_ReplaceItemInObject(cJSON_GetArrayItem(root, 1), "City", cJSON_CreateIntArray(ids, 4)); */

    if (print_preallocated(root) != 0) {
        cJSON_Delete(root);
        exit(EXIT_FAILURE);
    }
    cJSON_Delete(root);

    root = cJSON_CreateObject();
    cJSON_AddNumberToObject(root, "number", 1.0 / zero);

    if (print_preallocated(root) != 0) {
        cJSON_Delete(root);
        exit(EXIT_FAILURE);
    }
    cJSON_Delete(root);
}

下面是对调用cJSON库创建JSON对象,并增加键值对和子对象的代码调用情况:

CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void)
{
    cJSON *item = cJSON_New_Item(&global_hooks);
    if (item)
    {
        item->type = cJSON_Object;
    }

    return item;
}

static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc };

/*这里用static来修饰能够指定函数链接类型为内部链接,意味着该函数
只能在当前编译单元中使用,无法被其他编译单元引用,可以壁面函数名
冲突和符号重定义的问题,增强代码封装性和模块化。*/
static cJSON *cJSON_New_Item(const internal_hooks * const hooks)
{
    /*通过global_hooks,allocate指向internal_malloc,追溯后发现调用的stdlib中de 
    malloc函数*/
    cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON));
    if (node)
    {
        memset(node, '\0', sizeof(cJSON));    //用0初始化node指针指向的空间
    }

    return node;
}

typedef struct internal_hooks
{
    /*下面是函数指针,
    void *(CJSON_CDECL *allocate)(size_t size);                    //分配内存函数
    void (CJSON_CDECL *deallocate)(void *pointer);                 //释放内存函数
    void *(CJSON_CDECL *reallocate)(void *pointer, size_t size);   //重新分配内存函数
} internal_hooks;

/*左移运算符:a<<b,将a左移b位,这里用到的cJSON_Object左移后对应十进制是64
不直接定义整数是为了使语义更加清晰和易读并且有一定语义意义
例如,使用 1 << 6 来表示 cJSON_Object,我们可以明确地知道它是一个二进制数,
在第 7 位上有一个 1,而其他位都是 0。这样的表达方式更加直观且易于阅读。

此外,通过左移运算符,我们可以方便地组合多个标识位,使用按位或运算符将它们合
并在一起,形成复合类型的标识。这在处理复杂的标识位掩码时非常有用,使代码更
具可读性和可维护性。
*/
#define cJSON_Invalid (0)
#define cJSON_False  (1 << 0)
#define cJSON_True   (1 << 1)
#define cJSON_NULL   (1 << 2)
#define cJSON_Number (1 << 3)
#define cJSON_String (1 << 4)
#define cJSON_Array  (1 << 5)
#define cJSON_Object (1 << 6)
#define cJSON_Raw    (1 << 7) /* raw json */

其中的函数关系如下图所示:

 到此为止我们已经为初始化了root,现在为它添加item(cJSON)数据:

CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item)
{
    return add_item_to_object(object, string, item, &global_hooks, false);
}

/目标对象,键名,要添加的项,内部钩子,键名是否为常量*/
static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key)
{
    char *new_key = NULL;
    int new_type = cJSON_Invalid;

    if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item))
    {
        return false;
    }

    /*此处if和else如果是常量就设置标志位,反之取消标志位*/
    if (constant_key)
    {
        /*将`string`转换为非常量字符指针,赋值给new_key*/
        new_key = (char*)cast_away_const(string);
        /*这里用`|`是按位或,可以同时保存原始类型和标志位,将多个信息组合到一个整数
        例如,`item->type`=1000,`cJSON_STRINGIsConst`=0100,或的结果就是1100用
        一位二进制保存了状态信息*/
        new_type = item->type | cJSON_StringIsConst;
    }
    else
    {
        /*将`string`复制到一个新的动态分配的内存空间并检查是否成功分配*/
        new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks);
        if (new_key == NULL)
        {
            return false;
        }

        new_type = item->type & ~cJSON_StringIsConst;
    }

    if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))
    {
        hooks->deallocate(item->string);
    }

    item->string = new_key;
    item->type = new_type;

    return add_item_to_array(object, item);
}

static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks)
{
    size_t length = 0;
    unsigned char *copy = NULL;

    if (string == NULL)
    {
        return NULL;
    }

    length = strlen((const char*)string) + sizeof("");
    copy = (unsigned char*)hooks->allocate(length);
    if (copy == NULL)
    {
        return NULL;
    }
    memcpy(copy, string, length);

    return copy;
}

static void* cast_away_const(const void* string)
{
    return (void*)string;
}


/*这里的加入item,注意childe并不是一个头节点,只是它的prev并没有指向根节点(root)
而是指向child链表中的最后一个节点,简直是米奇妙妙屋-妙到家了!*/
static cJSON_bool add_item_to_array(cJSON *array, cJSON *item)
{
    cJSON *child = NULL;

    if ((item == NULL) || (array == NULL) || (array == item))
    {
        return false;
    }
    
    child = array->child;
    /*
     * To find the last item in array quickly, we use prev in array
     */
    if (child == NULL)
    {
        /* list is empty, start new one */
        array->child = item;
        item->prev = item;
        item->next = NULL;
    }
    else
    {
        /* append to the end */
        if (child->prev)
        {
            suffix_object(child->prev, item);
            array->child->prev = item;
        }
    }

    return true;
}

static void suffix_object(cJSON *prev, cJSON *item)
{
    prev->next = item;
    item->prev = prev;
}

函数关系如下图所示:

接下来是添加string、number、false等数据类型,步骤都是一样的(注意cJSON_AddItemToObject函数是增加本JSON的属性,而cJSON_Add*ToObject函数是增加嵌套的属性):

这里提一嘴,在执行上面函数时,出现分配内存失败时会调用Delete函数释放内存!!! 

 

猜你喜欢

转载自blog.csdn.net/pan_1214_/article/details/130694094