当前位置:   article > 正文

windows c++ cjson 使用_cJSON源码剖析

申请一个空的cjson数据结构内存空间,然后将其成员

1. 概述

     cJSON源码非常简单,即使是最新版本的cJSON,其 cJSON.c文件也仅有 750 多行的代码, cJSON.h文件 200 行代码不到。其.h文件和.c文件总代码量不超过 1000 行,非常简洁,阅读也很轻松。本文着重分析其设计框架和原理。至于其使用的一些细节,可以 [参考JSON官网]。

自从RFC 7159作出更新,合法JSON文件的根可以是任何类型的JSON值。而在较早的RFC 4627中,根值只允许是Object或Array。这是特别值得关注的地方,因为我碰到一些同事,总数习惯性的认为JSON就只能是 “对象{}”或“数组[]”,并不是这样的。JSON 中的值可以是对象、数组、字符串、数值(true、false或null),如图1所示:

fa0c45cead17e3d0fc8424cad7c90a7b.png

图1 JSON所支持的所有值类型列表 cJSON.h头文件也对其进行了宏定义,如下:

  1. #define cJSON_False 0 //布尔值 false
  2. #define cJSON_True 1 //布尔值 true
  3. #define cJSON_NULL 2 //NULL
  4. #define cJSON_Number 3 //数字
  5. #define cJSON_String 4 //字符串
  6. #define cJSON_Array 5 //数组
  7. #define cJSON_Object 6 //对象

2. cJSON框架剖析

     cJOSN设计的核心是采用了双向链表。当JSON句柄是一个对象或是数组的时候,将cJSON项不断的链接在cJSON句柄中。其数据结构定义为如下:

  1. typedef struct cJSON {
  2. struct cJSON *next,*prev; //next/prev允许您遍历数组/对象链.或者,使用use GetArraySize/GetArrayItem/GetObjectItem
  3. struct cJSON *child; //数组或对象项将有一个子指针指向数组/对象中的项链
  4. int type; //cJSON项类型,如上所示
  5. char *valuestring; /* The item's string, if type==cJSON_String */
  6. int valueint; /* The item's number, if type==cJSON_Number */
  7. double valuedouble; /* The item's number, if type==cJSON_Number */
  8. char *string; //项的名称字符串(如果此项是对象的子项或位于对象的子项列表中)
  9. } cJSON;

cJSON的使用,总是离不开两个话题: (1)组装JSON报文,网络发送,不同进程通信

6abbf979a3084557cce51bae957335e3.png

图2 将JSON格式化为字符串格式的文本

(2)解析JSON报文,获取接收的数据

e18b0b8507b27ad05094b1b2a659c0e9.png

图3 解析格式化后的JSON报文 因此,将所有cJSON.h文件中的代码分成两大类开始说明,分别是:JSON组装相关和JSON解析相关。首先是和组织JSON相关接口API系列。

  1. / 1.创建基本类型的JSON //
  2. cJSON *cJSON_CreateNull(void);
  3. cJSON *cJSON_CreateTrue(void);
  4. cJSON *cJSON_CreateFalse(void);
  5. cJSON *cJSON_CreateBool(int b);
  6. cJSON *cJSON_CreateNumber(double num);
  7. cJSON *cJSON_CreateString(const char *string);
  8. cJSON *cJSON_CreateArray(void);
  9. cJSON *cJSON_CreateObject(void);
  10. // 2.当JSON是Object/Array类型且嵌套了json项 //
  11. void cJSON_AddItemToArray(cJSON *array, cJSON *item); //将一个json项添加到json数组中去
  12. //将一个json项以string为关键字添加到object JSON对象中
  13. void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item);
  14. / 3.用于快速创建内容的宏 //
  15. #define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull())
  16. #define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue())
  17. #define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse())
  18. #define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b))
  19. #define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n))
  20. #define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s))
  21. / 4.将JSON格式化为文本字符串 //
  22. char *cJSON_Print(cJSON *item);
  23. //将cJSON实体呈现为文本以进行传输/存储,无需任何格式.完成后释放char*
  24. char *cJSON_PrintUnformatted(cJSON *item);

2.1 创建一个基本数据类型(数组、空值、布尔值、字符串、空数组和空对象)的JSON

(1) 创建空值 JSON

     申请一个空的cJSON数据结构内存空间,然后将其成员type置为cJSON_NULL类型。

cJSON *cJSON_CreateNull(void)	{cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_NULL;return item;

599d7a7cd06e1f3e6c43797337b9cc8a.png

图4 创建基本类型(空值)JSON

     将cJSON数据节点文本格式化处理,其过程如下。比如待文本格式化的JSON为一个空值类型,则直接申请 5字节空间,拷贝null字符串并格式化为最终文本。

  1. char *cJSON_Print(cJSON *item) {return print_value(item,0,1,0);}
  2. static char *print_value(cJSON *item,int depth,int fmt,printbuffer *p)
  3. {
  4. char *out=0;
  5. if (!item) return 0;
  6. if (p)
  7. {
  8. //cJSON_PrintBuffered函数使用if分支
  9. }
  10. else
  11. {
  12. switch ((item->type)&255)
  13. {
  14. //空值JSON逻辑分支处理(为了使排版尽量简洁,只保留了type为cJSON_NULL的case分支处理)
  15. case cJSON_NULL: out=cJSON_strdup("null"); break;
  16. }
  17. }
  18. return out;
  19. }

cJSON_strdup函数实现的就是一个标准函数 strdup()的功能,这里只所以要进行重实现,是为了提高代码的可移植性,因为某些平台是没有strdup这个标注函数的。 比如:ARM C编译器就没有strdup功能。其内部实现如下:

  1. static char* cJSON_strdup(const char* str)
  2. {
  3. size_t len;
  4. char* copy;
  5. len = strlen(str) + 1;
  6. if (!(copy = (char*)cJSON_malloc(len))) return 0;
  7. memcpy(copy,str,len);
  8. return copy;
  9. }

其余几个内部实现原理相同,这里不再赘述。


2.2 创建一个对象(Object)类型的JSON

     此例较简单,因为JSON对象中没有再继续嵌套数组/对象等项,仅在JSON对象中插入3个键值对。其key分别是:age, name和address。代码如下:

  1. #include <stdio.h>
  2. #include <assert.h>
  3. #include <stdlib.h>
  4. #include "cJSON.h"
  5. int main()
  6. {
  7. cJSON *p = cJSON_CreateObject();
  8. assert(p);
  9. cJSON_AddNumberToObject(p, "age", 26);
  10. cJSON_AddStringToObject(p, "name", "lixiaogang5");
  11. cJSON_AddStringToObject(p, "address", "guizhousheng");
  12. char *pBuf = cJSON_Print(p);
  13. printf("n%sn", pBuf);
  14. cJSON_Delete(p);
  15. free(pBuf);
  16. return 0;
  17. }

打印结果如图4所示

c8d7070f0383cc7c737b824f9aed6ef0.png

图5 打印格式化为文本字符串后的JSON对象值

当向该JSON对象中,嵌入三个简单类型的键值对时候,其原理图如图5所示。

fa700c4680d206cd3784c4cd3ac32d65.png

图6 打印格式化为文本字符串后的JSON对象值 备注:上图中左下方处 valuestring:“GZ”, 应修改“guizhousheng”。同时图片中没有显著标明的地方其成员值为0,因为在创建每个简单JSON基本类型(指: true、false、null、number、string、object和array)时 ,会先申请一个JOSN数据结构的内存空间,然后再memset为0。如图6所示

511cc328b07f57a570ef0dbeeebe5a86.png

图7 cJSON_New_Item 申请内存空间并memset

2.3 创建一个 数组(Array)类型的JSON

  1. cJSON *pRootArr = cJSON_CreateArray();
  2. assert(pRootArr);
  3. cJSON_AddItemToArray(pRootArr, cJSON_CreateString("Sunday"));
  4. cJSON_AddItemToArray(pRootArr, cJSON_CreateString("Monday"));
  5. cJSON_AddItemToArray(pRootArr, cJSON_CreateString("Tuesday"));
  6. cJSON_AddItemToArray(pRootArr, cJSON_CreateString("Wednesday"));
  7. cJSON_AddItemToArray(pRootArr, cJSON_CreateString("Thursday"));
  8. cJSON_AddItemToArray(pRootArr, cJSON_CreateString("Friday"));
  9. cJSON_AddItemToArray(pRootArr, cJSON_CreateString("Saturday"));
  10. char *serialPcBuf = cJSON_Print(pRootArr);
  11. printf("n%sn", serialPcBuf);
  12. cJSON_Delete(pRootArr);
  13. free(serialPcBuf);

打印序列化为文本字符串后的JSON如图7所示。其内部原理和图5一样,因此这里不再赘述,详情请参考图片5.

8fb2eee363b1b975d057ce63742db49f.png

图8 数组类型的JSON报文


2.4 创建一个对象类型的JSON, JSON内部嵌套数组

  1. cJSON *pRoot = cJSON_CreateObject(); //pRoot -1
  2. assert(pRoot);
  3. cJSON_AddItemToObject(pRoot, "name", cJSON_CreateString("lixiaogang5"));
  4. cJSON_AddItemToObject(pRoot, "sex", cJSON_CreateNumber(0)); //0-男 1-女
  5. cJSON *pSubObj = cJSON_CreateObject(); //pSubObj - 2
  6. assert(pSubObj);
  7. cJSON_AddItemToObject(pSubObj, "ReadingBook", cJSON_CreateString("C++"));
  8. cJSON *pSubArr = cJSON_CreateArray(); //pSubArr - 3
  9. assert(pSubArr);
  10. cJSON *pDataObj = cJSON_CreateObject(); //pDataObj - 4
  11. cJSON_AddItemToObject(pDataObj, "B1", cJSON_CreateString("C++ Primer"));
  12. cJSON_AddItemToObject(pDataObj, "B2", cJSON_CreateString("C++ 沉思录"));
  13. cJSON_AddItemToObject(pDataObj, "B3", cJSON_CreateString("深入理解C++11"));
  14. cJSON_AddItemToArray(pSubArr, pDataObj);
  15. cJSON_AddItemToObject(pSubObj, "info", pSubArr);
  16. cJSON_AddItemToObject(pRoot, "hobby", pSubObj);
  17. char *serialBuf = NULL;
  18. //serialBuf = cJSON_Print(pRoot);
  19. serialBuf = cJSON_PrintUnformatted(pRoot);
  20. printf("n%sn", serialBuf);
  21. cJSON_Delete(pRootArr); //释放cJSON申请的内存空间
  22. free(serialPcBuf);

打印结果如下:

91ffc989916ba5a621102d3ad8da68f5.png

JSON在线工具解析之后的数据为:

  1. {
  2. "name": "lixiaogang5",
  3. "sex": 0,
  4. "hobby": {
  5. "ReadingBook": "C++",
  6. "info": [
  7. {
  8. "B1": "C++ Primer",
  9. "B2": "C++ 沉思录",
  10. "B3": "深入理解C++11"
  11. }
  12. ]
  13. }
  14. }

如上JSON报文其内部数据结构原理图如图8所示。即使JSON内嵌套若干层级的数组或对象也是同样的原理。从原理图可以看到,当cJSON报文较大时候,即内部嵌套了N层数组,然后在数组下又嵌套数组时候,对内存空间的占用与消耗还是挺大的。比如cJSON官网有如下这段描述:

6dc2092dd9b1d157c1e874fe1acb56c3.png

图9 对象类型的JSON内嵌对象和数组原理图

数组和对象的深层嵌套:cJSON不支持嵌套太深的数组和对象,因为这会导致堆栈溢出。为了防止这种CJSON_NESTING_LIMIT情况,默认情况下,cJSON将深度限制为1000,但是可以在编译时进行更改。

分析:cJSON数据结构中,同时有3个指针,分别是 prev、next和child。设计3个指针有何用处? 什么时候用到 child,什么时候用到prev和next?

0cd658d9b48762b9823e15cf0741b6e6.png

图10 cJSON数据结构图 结论:当创建简单的数据类型(如:false、true、null和number)时候,是不会用到这3个指针的,只有在创建对象/数组类型且内部嵌套了其他满足json条件的基本类型/复杂类型时候才会用到。首先,当json对象有嵌套时候,第一个JSON数据一定是使用的child指针。即头结点的第一个子节点。其次,后续的json数据项都用到的是next和prev指针。设计next和prev指针是为了遍历方便,从某个结点开始,即可以向前遍历,也可以向后遍历,这是双向链表的优势。它弥补了单向链表只能单向访问/遍历的劣势。见名知意,child指针是JSON对象头结点或JSON数组头结点之后子节点的指向。参考图8。其次,从JSON报文解析APIcJSON_GetArraySize、cJSON_GetArrayItem和cJSON_GetObjectItem也能看出其用法,如图11所示。

1d3b65f09ec320869f13712fb178b80a.png

图11 cJSON报文解析相关API


2.5 创建一个 对象(Object)类型且内部嵌套数组,数组中又嵌套对个对象的JSON

     其最终的JSON格式化报文为:

a1c2fab94f3e984e9dd5538e1f4b1b50.png
  1. /// 2.5 示例代码 /
  2. unsigned i = 0;
  3. char buf[10] = {0};
  4. cJSON *pRoot = cJSON_CreateObject();
  5. assert(pRoot);
  6. cJSON *prootArr = cJSON_CreateArray();
  7. assert(prootArr);
  8. for(i = 0; i < 3; ++i)
  9. {
  10. cJSON *pDataObj = cJSON_CreateObject();
  11. memset(buf, 0x00, sizeof(buf));
  12. snprintf(buf, sizeof(buf), "Key_%d", i);
  13. cJSON_AddNumberToObject(pDataObj, buf, i);
  14. cJSON_AddItemToArray(prootArr, pDataObj);
  15. }
  16. cJSON_AddItemToObject(pRoot, "jsonData", prootArr);
  17. char *serialBuf = NULL;
  18. serialBuf = cJSON_Print(pRoot);
  19. //serialBuf = cJSON_PrintUnformatted(pRoot);
  20. printf("n%sn", serialBuf);
  21. cJSON_Delete(pRoot);
  22. free(serialBuf);

     内部各cJSON数据结构节点之间的关联关系如图12所示 各父节点(根节点)和第一个直接关联的子节点之间是通过 child 指针进行关联的,正如 2.4节中的结论。

528ca4443b02da4ada49dcf17bd9868e.png

图12 各cJSON数据结构节点之间内部关联关系

      使用 cJSON_Print 文本格式化该JSON报文的详细流程如下图13:

10d41dfbbcea1553c986be1d884232bb.png

图13 格式化JSON内部逻辑

总体看,主要可分为两个步骤/流程。其一,初始化数组部分, 其二,初始化最外层的对象部分+组装内部JSON数组部分。最后将第一部分的文本字符串和第二部分的文本字符串追加并根据 fmt 选项进行适当组装,便得到了最后的JSON格式化文本字符串。下面将更加详细的描述上面两个步骤中的每一个动作。

上面描述的两个流程分别对应下图14中两个红色虚线框中的部分。

ca81f2e0e7a90e915355468a43cac03a.png

图14 格式化JSON主要两个流程

     第一步,先初始化JSON的最后层(根节点pRoot)。备注:这里仅截取自 printf_object 函数API中最为核心的部分代码。用以说明这个JSON文本格式化字符串过程。

  1. //将所有结果收集到数组中. (1)child=item->child之后,child指向 prootArr 数据节点。
  2. child=item->child;depth++;if (fmt) len+=depth;
  3. while (child)
  4. {
  5. //i在定义时候初始化0,到这里时候能够保证i绝对=0 (2)申请key内存中间,并拷贝key到目的地址
  6. names[i]=str=print_string_ptr(child->string,0);
  7. /*print_vlaue非常核心的API (3)申请该key对应的value实体值,因为JSON的value可以是任意满足
  8. *JSON规则的数据类型, 所以这里的Value初始化调用 print_value函数,而该函数内部又会对当前的JSON
  9. *数据节点类型进行判断,以调用合适/对应的函数进行初始化操作。
  10. */
  11. entries[i++]=ret=print_value(child,depth,fmt,0); //注意: 前置++与后置++ 运算符规则及优先级
  12. if (str && ret) len+=strlen(ret)+strlen(str)+2+(fmt?2+depth:0); else fail=1;
  13. //遍历条件 (4)不断移动指针,使其指向下一个节点数据,根据其是否为NULL来判断当前节点是否为尾节点。
  14. child=child->next;
  15. }

child=item->child; 此时child指针指向pRootArr数据节点。遍历条件child=child->next;时,child为NULL,因此这里再格式化pRoot时候,这里循环只会走一次。得到结果如下所示。

dea1f2e0b1a436a1932f01fb1c104398.png

     第二步,初始化JSON数组部分(prootArr),

  1. //检索所有结果.
  2. child=item->child;
  3. while (child && !fail)
  4. {
  5. ret=print_value(child,depth+1,fmt,0);
  6. entries[i++]=ret;
  7. if (ret) len+=strlen(ret)+2+(fmt?1:0); else fail=1;
  8. child=child->next;
  9. }

2665d0ca59cea18d77fb2f21b39808a4.png

     上述两部结束之后,其结果如下图所示。

12fbb5297bdb5d2d20c7071d085a11d2.png

2.6 cJSON_Print 、cJSON_PrintUnformatted和cJSON_PrintBuffered 将组织好的JSON报文格式化为文本形式的字符串

     当JSON报文组织好之后,便可选择上述API之一将其格式化为文本字符串,以进行网络间传输交互通信。这3个API之间的差异如下:cJSON_Print 会对格式化后的JSON报文进行格式缩进,比如换行、tab缩进等,便于阅读。cJSON_PrintUnformatted 不带缩进和换行操作,整个JSON报文文本串被压缩成一个字符串,不便阅读,需借助一些在线工具进行格式化。cJSON_PrintBuffered 则使用了缓存策略将其JSON呈现为文本格式,至于是否进行缩进等格式化操作则根据用户的选择。其完整接口声明如下:

  1. //item -组织好的JSON prebuffer-用户预分配空间大小 fmt-是否格式化: 0-表示未格式化 1-表示格式化
  2. char *cJSON_PrintBuffered(cJSON *item,int prebuffer,int fmt);

本次着重讲解cJSON_Print函数接口,触类旁通,其他两个接口底层和cJSON_Print调用的一样。

  1. char *cJSON_Print(cJSON *item) {return print_value(item,0,1,0);}
  2. /**@fn print_value
  3. * @brief 根据JSON类型(type)格式化其对应的key和value
  4. * @param[in] item 待格式化的JSON对象
  5. * @param[in] depth JSON数组的深度
  6. * @param[in] fmt 是否进行格式化缩进 0-不换行 1-换行、空格
  7. * @param[out] NONE
  8. * return 文本格式化后的字符串
  9. */
  10. static char *print_value(cJSON *item,int depth,int fmt,printbuffer *p)
  11. {
  12. char *out=0;
  13. if (!item) return 0;
  14. if (p)
  15. {
  16. / cJSON_PrintBuffered函数API走该分支
  17. switch ((item->type)&255)
  18. {
  19. case cJSON_NULL: {out=ensure(p,5); if (out) strcpy(out,"null"); break;}
  20. case cJSON_False: {out=ensure(p,6); if (out) strcpy(out,"false"); break;}
  21. case cJSON_True: {out=ensure(p,5); if (out) strcpy(out,"true"); break;}
  22. case cJSON_Number: out=print_number(item,p);break;
  23. case cJSON_String: out=print_string(item,p);break;
  24. case cJSON_Array: out=print_array(item,depth,fmt,p);break;
  25. case cJSON_Object: out=print_object(item,depth,fmt,p);break;
  26. }
  27. }
  28. else
  29. {
  30. /// cJSON_Print和cJSON_PrintUnformatted走本分支 /
  31. switch ((item->type)&255)
  32. {
  33. case cJSON_NULL: out=cJSON_strdup("null"); break;
  34. case cJSON_False: out=cJSON_strdup("false");break;
  35. case cJSON_True: out=cJSON_strdup("true"); break;
  36. case cJSON_Number: out=print_number(item,0);break;
  37. case cJSON_String: out=print_string(item,0);break;
  38. case cJSON_Array: out=print_array(item,depth,fmt,0);break;
  39. case cJSON_Object: out=print_object(item,depth,fmt,0);break;
  40. }
  41. }
  42. return out;
  43. }

print_value 是最为核心的接口 API, 特别是当JSON对象内部有进行嵌套操作的时候,该函数会被循环调用,直到遍历该JSON对象的最后一个cjson数据结构对象节点为止。对于JSON基本类型(数组、布尔值、控制、空对象和空数组等)在前面已经有说明,下面着中介绍对象/数组内部有嵌套的复杂类型情况。 这里仍然以2.4节的JSON报文为例来说明是如何将一个cJSON报文格式化为最终可打印的文本字符串的完整过程。JSON中每个key和value是通过二级指针来动态申请内存空间进行分配值的,如图12和图13所示。

3e674e0fb6bbc2497799eed7a1e85d2b.png

图12 根据key大小申请对应的内存空间并拷贝key到目的地址

687df40759d217a9eeaec933ab567c6c.png

图13 根据value大小申请对应的内存空间并拷贝value到目的地址

     不管JSON内部嵌套了多少层级的JSON对象或数组,其key永远都是字符串形式,而value虽然可以是任意满足json条件的数据,但是其内部细分之后,仍然是JSON中的基本类型,比如数值、布尔、字符串等。因此cJSON中的核心思想是采用二级指针来为key和value动态分配内存空间,并将响应值拷贝到目的地址之后而进行文本格式化操作。至于最终呈现出来的完整格式化JSON文本,这要归功于cJSON数据结构设计中的type(每个cJSON数据结构在Create时候,都会初始化其相应的type成员)成员,如图14所示。(print_value)

5fce1fa0c1784ee5678c4694abe5f87f.png

图14 创建JSON基本类型时候,初始化其对应的type成员

     因此,在格式化的时候,首先第一步便是判断其cJSON的数据类型,若为普通类型,则直接使用上图12和13的方式便可格式化对应的键值对。print_array 和 print_object是最为重要的两个函数API。

67abde9a428ecc7cff26beff7cabb3a8.png

图15 循环遍历并格式化object/array中的数据

print_object API内部实现

  1. //将对象呈现为文本(item: JSON . depth-0 . fmt-1. p-是否缓存空间) 0-1-0
  2. static char *print_object(cJSON *item,int depth,int fmt/*1-格式化 0-不格式化*/,printbuffer *p)
  3. {
  4. char **entries=0,**names=0;
  5. char *out=0,*ptr,*ret,*str;int len=7,i=0,j;
  6. cJSON *child=item->child;
  7. int numentries=0,fail=0;
  8. size_t tmplen=0;
  9. //计算实体的数量
  10. while (child) numentries++,child=child->next;
  11. //显示处理空对象案例
  12. if (!numentries)
  13. {
  14. //如果JSON对象为空,则直接构建"|{|n|}|0|"返回.最后一个字节存放字符串结束符'0'.
  15. //如果选择不格式.其结果是:|{|}|0| -->只需要3字节便可. 没有换行缩进等字符占用空间
  16. if (p) out=ensure(p,fmt?depth+4:3);
  17. else out=(char*)cJSON_malloc(fmt?depth+4:3); //如果格式化,申请4字节内存空间; 反之申请3字节内存空间
  18. if (!out) return 0;
  19. ptr=out;*ptr++='{';
  20. if (fmt) {*ptr++='n';for (i=0;i<depth-1;i++) *ptr++='t';}
  21. *ptr++='}';*ptr++=0;
  22. return out;
  23. }
  24. if (p)
  25. {
  26. //合并输出. 若使用了内存缓存策略,则走本分支.
  27. i=p->offset;
  28. len=fmt?2:1; ptr=ensure(p,len+1); if (!ptr) return 0;
  29. *ptr++='{'; if (fmt) *ptr++='n'; *ptr=0; p->offset+=len;
  30. child=item->child;depth++;
  31. while (child)
  32. {
  33. if (fmt)
  34. {
  35. ptr=ensure(p,depth); if (!ptr) return 0;
  36. for (j=0;j<depth;j++) *ptr++='t';
  37. p->offset+=depth;
  38. }
  39. print_string_ptr(child->string,p);
  40. p->offset=update(p);
  41. len=fmt?2:1;
  42. ptr=ensure(p,len); if (!ptr) return 0;
  43. *ptr++=':';if (fmt) *ptr++='t';
  44. p->offset+=len;
  45. print_value(child,depth,fmt,p);
  46. p->offset=update(p);
  47. len=(fmt?1:0)+(child->next?1:0);
  48. ptr=ensure(p,len+1); if (!ptr) return 0;
  49. if (child->next) *ptr++=',';
  50. if (fmt) *ptr++='n';*ptr=0;
  51. p->offset+=len;
  52. child=child->next;
  53. }
  54. ptr=ensure(p,fmt?(depth+1):2); if (!ptr) return 0;
  55. if (fmt) for (i=0;i<depth-1;i++) *ptr++='t';
  56. *ptr++='}';*ptr=0;
  57. out=(p->buffer)+i;
  58. }
  59. else
  60. {
  61. /为名称和对象分配空间/
  62. entries=(char**)cJSON_malloc(numentries*sizeof(char*));
  63. if (!entries) return 0;
  64. names=(char**)cJSON_malloc(numentries*sizeof(char*));
  65. if (!names) {cJSON_free(entries);return 0;}
  66. //建议将CJSON_malloc内部构建时候采用realloc或calloc,这样便不必再次memset,省去开销
  67. memset(entries,0,sizeof(char*)*numentries);
  68. memset(names,0,sizeof(char*)*numentries);
  69. //将所有结果收集到数组中.
  70. child=item->child;depth++;if (fmt) len+=depth;
  71. while (child)
  72. {
  73. //i在定义时候初始化0,到这里时候能够保证i绝对=0
  74. names[i]=str=print_string_ptr(child->string,0);
  75. //print_vlaue非常核心的API
  76. entries[i++]=ret=print_value(child,depth,fmt,0); //注意: 前置++与后置++ 运算符规则及优先级
  77. if (str && ret) len+=strlen(ret)+strlen(str)+2+(fmt?2+depth:0); else fail=1;
  78. //遍历条件
  79. child=child->next;
  80. }
  81. //尝试分配输出字符串
  82. if (!fail) out=(char*)cJSON_malloc(len);
  83. if (!out) fail=1;
  84. //处理失败
  85. if (fail)
  86. {
  87. //释放二级、一级指针所对应申请的内存空间
  88. for (i=0;i<numentries;i++) {if (names[i]) cJSON_free(names[i]);if (entries[i]) cJSON_free(entries[i]);}
  89. cJSON_free(names);cJSON_free(entries);
  90. return 0;
  91. }
  92. //组成并输出
  93. *out='{';ptr=out+1;if (fmt)*ptr++='n';*ptr=0;
  94. for (i=0;i<numentries;i++)
  95. {
  96. if (fmt) for (j=0;j<depth;j++) *ptr++='t';
  97. tmplen=strlen(names[i]);memcpy(ptr,names[i],tmplen);ptr+=tmplen;
  98. *ptr++=':';if (fmt) *ptr++='t';
  99. strcpy(ptr,entries[i]);ptr+=strlen(entries[i]);
  100. if (i!=numentries-1) *ptr++=',';
  101. if (fmt) *ptr++='n';*ptr=0;
  102. cJSON_free(names[i]);cJSON_free(entries[i]);
  103. }
  104. cJSON_free(names);cJSON_free(entries);
  105. if (fmt) for (i=0;i<depth-1;i++) *ptr++='t';
  106. *ptr++='}';*ptr++=0;
  107. //printf("out:n%sn", ptr);
  108. }
  109. return out;
  110. }

     该函数内部的 while 部分代码会遍历该对象(通过后继指针)下的每个字节点,直到 child = child->next中child指针为空的时候,才停止,并将所有的这些已经字符串的数据组合为一个完整的对象。

31165d26c93bbbe8df2c6b8b57fd490e.png

print_array API内部实现

  1. //将数组呈现为文本
  2. static char *print_array(cJSON *item,int depth,int fmt,printbuffer *p)
  3. {
  4. char **entries;
  5. char *out=0,*ptr,*ret;int len=5;
  6. cJSON *child=item->child;
  7. int numentries=0,i=0,fail=0;
  8. size_t tmplen=0;
  9. //数组中有多少项?
  10. while (child) numentries++,child=child->next;
  11. //处理当数组为空的时候. 直接格式化为: out = |[|]|0|
  12. if (!numentries)
  13. {
  14. if (p) out=ensure(p,3);
  15. else out=(char*)cJSON_malloc(3);
  16. if (out) strcpy(out,"[]");
  17. return out;
  18. }
  19. if (p)
  20. {
  21. /* Compose the output array. */
  22. i=p->offset;
  23. ptr=ensure(p,1);if (!ptr) return 0; *ptr='['; p->offset++;
  24. child=item->child;
  25. while (child && !fail)
  26. {
  27. print_value(child,depth+1,fmt,p);
  28. p->offset=update(p);
  29. if (child->next) {len=fmt?2:1;ptr=ensure(p,len+1);if (!ptr) return 0;*ptr++=',';if(fmt)*ptr++=' ';*ptr=0;p->offset+=len;}
  30. child=child->next;
  31. }
  32. ptr=ensure(p,2);if (!ptr) return 0; *ptr++=']';*ptr=0;
  33. out=(p->buffer)+i;
  34. }
  35. else
  36. {
  37. printf("entries[%d]n", numentries);
  38. //申请/分配一个数组来保存其中的每个值
  39. entries=(char**)cJSON_malloc(numentries*sizeof(char*));
  40. if (!entries) return 0;
  41. memset(entries,0,numentries*sizeof(char*));
  42. //检索所有结果.
  43. child=item->child;
  44. while (child && !fail)
  45. {
  46. ret=print_value(child,depth+1,fmt,0);
  47. entries[i++]=ret;
  48. if (ret) len+=strlen(ret)+2+(fmt?1:0); else fail=1;
  49. child=child->next;
  50. }
  51. //如果没有失败,尝试malloc输出字符串
  52. if (!fail) out=(char*)cJSON_malloc(len);
  53. //如果malloc失败,则失败并结束
  54. if (!out) fail=1;
  55. //处理失败,则释放申请的cJSON内存空间
  56. if (fail)
  57. {
  58. for (i=0;i<numentries;i++) if (entries[i]) cJSON_free(entries[i]);
  59. cJSON_free(entries);
  60. return 0;
  61. }
  62. //组合并输出数组
  63. *out='[';
  64. ptr=out+1;*ptr=0;
  65. for (i=0;i<numentries;i++)
  66. {
  67. tmplen=strlen(entries[i]);memcpy(ptr,entries[i],tmplen);ptr+=tmplen;
  68. if (i!=numentries-1) {*ptr++=',';if(fmt)*ptr++=' ';*ptr=0;}
  69. cJSON_free(entries[i]);
  70. }
  71. cJSON_free(entries);
  72. *ptr++=']';*ptr++=0;
  73. }
  74. printf("out:n%sn", out);
  75. return out;
  76. }

     对于该函数接口,内部最为重要的核心部分便是如下 while 循环内部的内容。JSON数组中可以嵌套满足任意数量的符合JSON规则的数据类型(包括:数组、布尔、空值、字符串、对象等),每次循环遍历的时候,都会调用 print_value 函数,而该函数内部会对每个cJSON数据对象进行 type判断,循环递归进行该操作,直到达到该数组的最后一个子节点为止。

a50843f3c34f8aa5f518f491ba3a35f5.png

其详细格式化流程参考图16。需再次强调的是:JSON格式中其 Key永远是字符串,Value 可以是满足JSON规则的任务基本类型/复杂嵌套类型。

872e3680a96ff5bb5aace7d9412d6a09.png

图16 对象/数组内部嵌套对象/数组格式化流程


3. 将文本格式化后的JSON文本解析为cJSON数据结构

主要 cJSON_Parse 函数。其声明如下:

  1. // cJSON.h文件定义
  2. extern cJSON *cJSON_Parse(const char *value);
  3. // cJSON.c函数功能实现
  4. cJSON *cJSON_Parse(const char *value) {return cJSON_ParseWithOpts(value,0,0);}
  5. /
  6. cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated)
  7. {
  8. const char *end=0;
  9. cJSON *c=cJSON_New_Item();
  10. ep=0;
  11. if (!c) return 0; //memory失败
  12. end=parse_value(c,skip(value));
  13. //解析失败,ep设置相应的错误码信息
  14. if (!end) {cJSON_Delete(c);return 0;}
  15. //如果需要以空结尾的JSON而不附加垃圾,则跳过并且然后检查是否有空结束符
  16. if (require_null_terminated) {end=skip(end);if (*end) {cJSON_Delete(c);ep=end;return 0;}}
  17. if (return_parse_end) *return_parse_end=end;
  18. return c;
  19. }

     另外三个相关联的函数分别是 cJSON_GetArraySize、cJSON_GetArrayItem和cJSON_GetObjectItem。将格式化后的JSON转换为cJSON数据结构是将cJSON格式化文本的一个逆过程。其相关接口和实现比较简单,因此这里不进行说明。

  1. //返回数组(或对象)中的项数
  2. extern int cJSON_GetArraySize(cJSON *array);
  3. //从数组"array"中检索项目号"item",如果不成功,则返回NULL
  4. extern cJSON *cJSON_GetArrayItem(cJSON *array,int item);
  5. //从对象中获取项"string".不区分大小写
  6. extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string);

本文来自个人博客 ·【此处不归牛顿管

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/119551
推荐阅读
相关标签
  

闽ICP备14008679号