赞
踩
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所示:
图1 JSON所支持的所有值类型列表 cJSON.h头文件也对其进行了宏定义,如下:
- #define cJSON_False 0 //布尔值 false
- #define cJSON_True 1 //布尔值 true
- #define cJSON_NULL 2 //NULL值
- #define cJSON_Number 3 //数字
- #define cJSON_String 4 //字符串
- #define cJSON_Array 5 //数组
- #define cJSON_Object 6 //对象
cJOSN设计的核心是采用了双向链表。当JSON句柄是一个对象或是数组的时候,将cJSON项不断的链接在cJSON句柄中。其数据结构定义为如下:
- typedef struct cJSON {
- struct cJSON *next,*prev; //next/prev允许您遍历数组/对象链.或者,使用use GetArraySize/GetArrayItem/GetObjectItem
- struct cJSON *child; //数组或对象项将有一个子指针指向数组/对象中的项链
- int type; //cJSON项类型,如上所示
- char *valuestring; /* The item's string, if type==cJSON_String */
- int valueint; /* The item's number, if type==cJSON_Number */
- double valuedouble; /* The item's number, if type==cJSON_Number */
- char *string; //项的名称字符串(如果此项是对象的子项或位于对象的子项列表中)
- } cJSON;
cJSON的使用,总是离不开两个话题: (1)组装JSON报文,网络发送,不同进程通信
图2 将JSON格式化为字符串格式的文本
(2)解析JSON报文,获取接收的数据
图3 解析格式化后的JSON报文 因此,将所有cJSON.h文件中的代码分成两大类开始说明,分别是:JSON组装相关和JSON解析相关。首先是和组织JSON相关接口API系列。
- / 1.创建基本类型的JSON //
- cJSON *cJSON_CreateNull(void);
- cJSON *cJSON_CreateTrue(void);
- cJSON *cJSON_CreateFalse(void);
- cJSON *cJSON_CreateBool(int b);
- cJSON *cJSON_CreateNumber(double num);
- cJSON *cJSON_CreateString(const char *string);
- cJSON *cJSON_CreateArray(void);
- cJSON *cJSON_CreateObject(void);
- // 2.当JSON是Object/Array类型且嵌套了json项 //
- void cJSON_AddItemToArray(cJSON *array, cJSON *item); //将一个json项添加到json数组中去
-
- //将一个json项以string为关键字添加到object JSON对象中
- void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item);
-
- / 3.用于快速创建内容的宏 //
- #define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull())
- #define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue())
- #define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse())
- #define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b))
- #define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n))
- #define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s))
-
- / 4.将JSON格式化为文本字符串 //
- char *cJSON_Print(cJSON *item);
- //将cJSON实体呈现为文本以进行传输/存储,无需任何格式.完成后释放char*
- char *cJSON_PrintUnformatted(cJSON *item);
申请一个空的cJSON数据结构内存空间,然后将其成员type置为cJSON_NULL类型。
cJSON *cJSON_CreateNull(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_NULL;return item;
图4 创建基本类型(空值)JSON
将cJSON数据节点文本格式化处理,其过程如下。比如待文本格式化的JSON为一个空值类型,则直接申请 5字节空间,拷贝null字符串并格式化为最终文本。
- char *cJSON_Print(cJSON *item) {return print_value(item,0,1,0);}
- static char *print_value(cJSON *item,int depth,int fmt,printbuffer *p)
- {
- char *out=0;
- if (!item) return 0;
- if (p)
- {
- //cJSON_PrintBuffered函数使用if分支
- }
- else
- {
- switch ((item->type)&255)
- {
- //空值JSON逻辑分支处理(为了使排版尽量简洁,只保留了type为cJSON_NULL的case分支处理)
- case cJSON_NULL: out=cJSON_strdup("null"); break;
- }
- }
- return out;
- }
cJSON_strdup函数实现的就是一个标准函数 strdup()的功能,这里只所以要进行重实现,是为了提高代码的可移植性,因为某些平台是没有strdup这个标注函数的。 比如:ARM C编译器就没有strdup功能。其内部实现如下:
- static char* cJSON_strdup(const char* str)
- {
- size_t len;
- char* copy;
-
- len = strlen(str) + 1;
- if (!(copy = (char*)cJSON_malloc(len))) return 0;
- memcpy(copy,str,len);
- return copy;
- }
其余几个内部实现原理相同,这里不再赘述。
此例较简单,因为JSON对象中没有再继续嵌套数组/对象等项,仅在JSON对象中插入3个键值对。其key分别是:age, name和address。代码如下:
- #include <stdio.h>
- #include <assert.h>
- #include <stdlib.h>
- #include "cJSON.h"
- int main()
- {
- cJSON *p = cJSON_CreateObject();
- assert(p);
- cJSON_AddNumberToObject(p, "age", 26);
- cJSON_AddStringToObject(p, "name", "lixiaogang5");
- cJSON_AddStringToObject(p, "address", "guizhousheng");
- char *pBuf = cJSON_Print(p);
- printf("n%sn", pBuf);
- cJSON_Delete(p);
- free(pBuf);
- return 0;
- }
打印结果如图4所示
图5 打印格式化为文本字符串后的JSON对象值
当向该JSON对象中,嵌入三个简单类型的键值对时候,其原理图如图5所示。
图6 打印格式化为文本字符串后的JSON对象值 备注:上图中左下方处 valuestring:“GZ”, 应修改“guizhousheng”。同时图片中没有显著标明的地方其成员值为0,因为在创建每个简单JSON基本类型(指: true、false、null、number、string、object和array)时 ,会先申请一个JOSN数据结构的内存空间,然后再memset为0。如图6所示
图7 cJSON_New_Item 申请内存空间并memset
- cJSON *pRootArr = cJSON_CreateArray();
- assert(pRootArr);
-
- cJSON_AddItemToArray(pRootArr, cJSON_CreateString("Sunday"));
- cJSON_AddItemToArray(pRootArr, cJSON_CreateString("Monday"));
- cJSON_AddItemToArray(pRootArr, cJSON_CreateString("Tuesday"));
- cJSON_AddItemToArray(pRootArr, cJSON_CreateString("Wednesday"));
- cJSON_AddItemToArray(pRootArr, cJSON_CreateString("Thursday"));
- cJSON_AddItemToArray(pRootArr, cJSON_CreateString("Friday"));
- cJSON_AddItemToArray(pRootArr, cJSON_CreateString("Saturday"));
-
- char *serialPcBuf = cJSON_Print(pRootArr);
- printf("n%sn", serialPcBuf);
- cJSON_Delete(pRootArr);
- free(serialPcBuf);
打印序列化为文本字符串后的JSON如图7所示。其内部原理和图5一样,因此这里不再赘述,详情请参考图片5.
图8 数组类型的JSON报文
- cJSON *pRoot = cJSON_CreateObject(); //pRoot -1
- assert(pRoot);
- cJSON_AddItemToObject(pRoot, "name", cJSON_CreateString("lixiaogang5"));
- cJSON_AddItemToObject(pRoot, "sex", cJSON_CreateNumber(0)); //0-男 1-女
- cJSON *pSubObj = cJSON_CreateObject(); //pSubObj - 2
- assert(pSubObj);
- cJSON_AddItemToObject(pSubObj, "ReadingBook", cJSON_CreateString("C++"));
- cJSON *pSubArr = cJSON_CreateArray(); //pSubArr - 3
- assert(pSubArr);
-
- cJSON *pDataObj = cJSON_CreateObject(); //pDataObj - 4
- cJSON_AddItemToObject(pDataObj, "B1", cJSON_CreateString("C++ Primer"));
- cJSON_AddItemToObject(pDataObj, "B2", cJSON_CreateString("C++ 沉思录"));
- cJSON_AddItemToObject(pDataObj, "B3", cJSON_CreateString("深入理解C++11"));
- cJSON_AddItemToArray(pSubArr, pDataObj);
- cJSON_AddItemToObject(pSubObj, "info", pSubArr);
- cJSON_AddItemToObject(pRoot, "hobby", pSubObj);
-
- char *serialBuf = NULL;
- //serialBuf = cJSON_Print(pRoot);
- serialBuf = cJSON_PrintUnformatted(pRoot);
- printf("n%sn", serialBuf);
- cJSON_Delete(pRootArr); //释放cJSON申请的内存空间
- free(serialPcBuf);
打印结果如下:
JSON在线工具解析之后的数据为:
- {
- "name": "lixiaogang5",
- "sex": 0,
- "hobby": {
- "ReadingBook": "C++",
- "info": [
- {
- "B1": "C++ Primer",
- "B2": "C++ 沉思录",
- "B3": "深入理解C++11"
- }
- ]
- }
- }
如上JSON报文其内部数据结构原理图如图8所示。即使JSON内嵌套若干层级的数组或对象也是同样的原理。从原理图可以看到,当cJSON报文较大时候,即内部嵌套了N层数组,然后在数组下又嵌套数组时候,对内存空间的占用与消耗还是挺大的。比如cJSON官网有如下这段描述:
图9 对象类型的JSON内嵌对象和数组原理图
数组和对象的深层嵌套:cJSON不支持嵌套太深的数组和对象,因为这会导致堆栈溢出。为了防止这种CJSON_NESTING_LIMIT情况,默认情况下,cJSON将深度限制为1000,但是可以在编译时进行更改。
分析:cJSON数据结构中,同时有3个指针,分别是 prev、next和child。设计3个指针有何用处? 什么时候用到 child,什么时候用到prev和next?
图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所示。
图11 cJSON报文解析相关API
其最终的JSON格式化报文为:
- /// 2.5 示例代码 /
- unsigned i = 0;
- char buf[10] = {0};
-
- cJSON *pRoot = cJSON_CreateObject();
- assert(pRoot);
- cJSON *prootArr = cJSON_CreateArray();
- assert(prootArr);
- for(i = 0; i < 3; ++i)
- {
- cJSON *pDataObj = cJSON_CreateObject();
- memset(buf, 0x00, sizeof(buf));
- snprintf(buf, sizeof(buf), "Key_%d", i);
- cJSON_AddNumberToObject(pDataObj, buf, i);
- cJSON_AddItemToArray(prootArr, pDataObj);
- }
-
- cJSON_AddItemToObject(pRoot, "jsonData", prootArr);
- char *serialBuf = NULL;
- serialBuf = cJSON_Print(pRoot);
- //serialBuf = cJSON_PrintUnformatted(pRoot);
- printf("n%sn", serialBuf);
- cJSON_Delete(pRoot);
- free(serialBuf);
内部各cJSON数据结构节点之间的关联关系如图12所示 各父节点(根节点)和第一个直接关联的子节点之间是通过 child 指针进行关联的,正如 2.4节中的结论。
图12 各cJSON数据结构节点之间内部关联关系
使用 cJSON_Print 文本格式化该JSON报文的详细流程如下图13:
图13 格式化JSON内部逻辑
总体看,主要可分为两个步骤/流程。其一,初始化数组部分, 其二,初始化最外层的对象部分+组装内部JSON数组部分。最后将第一部分的文本字符串和第二部分的文本字符串追加并根据 fmt 选项进行适当组装,便得到了最后的JSON格式化文本字符串。下面将更加详细的描述上面两个步骤中的每一个动作。
上面描述的两个流程分别对应下图14中两个红色虚线框中的部分。
图14 格式化JSON主要两个流程
第一步,先初始化JSON的最后层(根节点pRoot)。备注:这里仅截取自 printf_object 函数API中最为核心的部分代码。用以说明这个JSON文本格式化字符串过程。
- //将所有结果收集到数组中. (1)child=item->child之后,child指向 prootArr 数据节点。
- child=item->child;depth++;if (fmt) len+=depth;
- while (child)
- {
- //i在定义时候初始化0,到这里时候能够保证i绝对=0 (2)申请key内存中间,并拷贝key到目的地址
- names[i]=str=print_string_ptr(child->string,0);
- /*print_vlaue非常核心的API (3)申请该key对应的value实体值,因为JSON的value可以是任意满足
- *JSON规则的数据类型, 所以这里的Value初始化调用 print_value函数,而该函数内部又会对当前的JSON
- *数据节点类型进行判断,以调用合适/对应的函数进行初始化操作。
- */
- entries[i++]=ret=print_value(child,depth,fmt,0); //注意: 前置++与后置++ 运算符规则及优先级
- if (str && ret) len+=strlen(ret)+strlen(str)+2+(fmt?2+depth:0); else fail=1;
- //遍历条件 (4)不断移动指针,使其指向下一个节点数据,根据其是否为NULL来判断当前节点是否为尾节点。
- child=child->next;
- }
child=item->child; 此时child指针指向pRootArr数据节点。遍历条件child=child->next;时,child为NULL,因此这里再格式化pRoot时候,这里循环只会走一次。得到结果如下所示。
第二步,初始化JSON数组部分(prootArr),
- //检索所有结果.
- child=item->child;
- while (child && !fail)
- {
- ret=print_value(child,depth+1,fmt,0);
- entries[i++]=ret;
- if (ret) len+=strlen(ret)+2+(fmt?1:0); else fail=1;
- child=child->next;
- }
上述两部结束之后,其结果如下图所示。
当JSON报文组织好之后,便可选择上述API之一将其格式化为文本字符串,以进行网络间传输交互通信。这3个API之间的差异如下:cJSON_Print 会对格式化后的JSON报文进行格式缩进,比如换行、tab缩进等,便于阅读。cJSON_PrintUnformatted 不带缩进和换行操作,整个JSON报文文本串被压缩成一个字符串,不便阅读,需借助一些在线工具进行格式化。cJSON_PrintBuffered 则使用了缓存策略将其JSON呈现为文本格式,至于是否进行缩进等格式化操作则根据用户的选择。其完整接口声明如下:
- //item -组织好的JSON prebuffer-用户预分配空间大小 fmt-是否格式化: 0-表示未格式化 1-表示格式化
- char *cJSON_PrintBuffered(cJSON *item,int prebuffer,int fmt);
本次着重讲解cJSON_Print函数接口,触类旁通,其他两个接口底层和cJSON_Print调用的一样。
- char *cJSON_Print(cJSON *item) {return print_value(item,0,1,0);}
- /**@fn print_value
- * @brief 根据JSON类型(type)格式化其对应的key和value
- * @param[in] item 待格式化的JSON对象
- * @param[in] depth JSON数组的深度
- * @param[in] fmt 是否进行格式化缩进 0-不换行 1-换行、空格
- * @param[out] NONE
- * return 文本格式化后的字符串
- */
- static char *print_value(cJSON *item,int depth,int fmt,printbuffer *p)
- {
- char *out=0;
- if (!item) return 0;
- if (p)
- {
- / cJSON_PrintBuffered函数API走该分支
- switch ((item->type)&255)
- {
- case cJSON_NULL: {out=ensure(p,5); if (out) strcpy(out,"null"); break;}
- case cJSON_False: {out=ensure(p,6); if (out) strcpy(out,"false"); break;}
- case cJSON_True: {out=ensure(p,5); if (out) strcpy(out,"true"); break;}
- case cJSON_Number: out=print_number(item,p);break;
- case cJSON_String: out=print_string(item,p);break;
- case cJSON_Array: out=print_array(item,depth,fmt,p);break;
- case cJSON_Object: out=print_object(item,depth,fmt,p);break;
- }
- }
- else
- {
- /// cJSON_Print和cJSON_PrintUnformatted走本分支 /
- switch ((item->type)&255)
- {
- case cJSON_NULL: out=cJSON_strdup("null"); break;
- case cJSON_False: out=cJSON_strdup("false");break;
- case cJSON_True: out=cJSON_strdup("true"); break;
- case cJSON_Number: out=print_number(item,0);break;
- case cJSON_String: out=print_string(item,0);break;
- case cJSON_Array: out=print_array(item,depth,fmt,0);break;
- case cJSON_Object: out=print_object(item,depth,fmt,0);break;
- }
- }
- return out;
- }
print_value 是最为核心的接口 API, 特别是当JSON对象内部有进行嵌套操作的时候,该函数会被循环调用,直到遍历该JSON对象的最后一个cjson数据结构对象节点为止。对于JSON基本类型(数组、布尔值、控制、空对象和空数组等)在前面已经有说明,下面着中介绍对象/数组内部有嵌套的复杂类型情况。 这里仍然以2.4节的JSON报文为例来说明是如何将一个cJSON报文格式化为最终可打印的文本字符串的完整过程。JSON中每个key和value是通过二级指针来动态申请内存空间进行分配值的,如图12和图13所示。
图12 根据key大小申请对应的内存空间并拷贝key到目的地址
图13 根据value大小申请对应的内存空间并拷贝value到目的地址
不管JSON内部嵌套了多少层级的JSON对象或数组,其key永远都是字符串形式,而value虽然可以是任意满足json条件的数据,但是其内部细分之后,仍然是JSON中的基本类型,比如数值、布尔、字符串等。因此cJSON中的核心思想是采用二级指针来为key和value动态分配内存空间,并将响应值拷贝到目的地址之后而进行文本格式化操作。至于最终呈现出来的完整格式化JSON文本,这要归功于cJSON数据结构设计中的type(每个cJSON数据结构在Create时候,都会初始化其相应的type成员)成员,如图14所示。(print_value)
图14 创建JSON基本类型时候,初始化其对应的type成员
因此,在格式化的时候,首先第一步便是判断其cJSON的数据类型,若为普通类型,则直接使用上图12和13的方式便可格式化对应的键值对。print_array 和 print_object是最为重要的两个函数API。
图15 循环遍历并格式化object/array中的数据
print_object API内部实现
- //将对象呈现为文本(item: JSON . depth-0 . fmt-1. p-是否缓存空间) 0-1-0
- static char *print_object(cJSON *item,int depth,int fmt/*1-格式化 0-不格式化*/,printbuffer *p)
- {
- char **entries=0,**names=0;
- char *out=0,*ptr,*ret,*str;int len=7,i=0,j;
- cJSON *child=item->child;
- int numentries=0,fail=0;
- size_t tmplen=0;
- //计算实体的数量
- while (child) numentries++,child=child->next;
- //显示处理空对象案例
- if (!numentries)
- {
- //如果JSON对象为空,则直接构建"|{|n|}|0|"返回.最后一个字节存放字符串结束符'0'.
- //如果选择不格式.其结果是:|{|}|0| -->只需要3字节便可. 没有换行缩进等字符占用空间
- if (p) out=ensure(p,fmt?depth+4:3);
- else out=(char*)cJSON_malloc(fmt?depth+4:3); //如果格式化,申请4字节内存空间; 反之申请3字节内存空间
- if (!out) return 0;
- ptr=out;*ptr++='{';
- if (fmt) {*ptr++='n';for (i=0;i<depth-1;i++) *ptr++='t';}
- *ptr++='}';*ptr++=0;
- return out;
- }
-
- if (p)
- {
- //合并输出. 若使用了内存缓存策略,则走本分支.
- i=p->offset;
- len=fmt?2:1; ptr=ensure(p,len+1); if (!ptr) return 0;
- *ptr++='{'; if (fmt) *ptr++='n'; *ptr=0; p->offset+=len;
- child=item->child;depth++;
- while (child)
- {
- if (fmt)
- {
- ptr=ensure(p,depth); if (!ptr) return 0;
- for (j=0;j<depth;j++) *ptr++='t';
- p->offset+=depth;
- }
- print_string_ptr(child->string,p);
- p->offset=update(p);
-
- len=fmt?2:1;
- ptr=ensure(p,len); if (!ptr) return 0;
- *ptr++=':';if (fmt) *ptr++='t';
- p->offset+=len;
-
- print_value(child,depth,fmt,p);
- p->offset=update(p);
-
- len=(fmt?1:0)+(child->next?1:0);
- ptr=ensure(p,len+1); if (!ptr) return 0;
- if (child->next) *ptr++=',';
- if (fmt) *ptr++='n';*ptr=0;
- p->offset+=len;
- child=child->next;
- }
- ptr=ensure(p,fmt?(depth+1):2); if (!ptr) return 0;
- if (fmt) for (i=0;i<depth-1;i++) *ptr++='t';
- *ptr++='}';*ptr=0;
- out=(p->buffer)+i;
- }
- else
- {
- /为名称和对象分配空间/
- entries=(char**)cJSON_malloc(numentries*sizeof(char*));
- if (!entries) return 0;
- names=(char**)cJSON_malloc(numentries*sizeof(char*));
- if (!names) {cJSON_free(entries);return 0;}
- //建议将CJSON_malloc内部构建时候采用realloc或calloc,这样便不必再次memset,省去开销
- memset(entries,0,sizeof(char*)*numentries);
- memset(names,0,sizeof(char*)*numentries);
-
- //将所有结果收集到数组中.
- child=item->child;depth++;if (fmt) len+=depth;
- while (child)
- {
- //i在定义时候初始化0,到这里时候能够保证i绝对=0
- names[i]=str=print_string_ptr(child->string,0);
- //print_vlaue非常核心的API
- entries[i++]=ret=print_value(child,depth,fmt,0); //注意: 前置++与后置++ 运算符规则及优先级
- if (str && ret) len+=strlen(ret)+strlen(str)+2+(fmt?2+depth:0); else fail=1;
- //遍历条件
- child=child->next;
- }
-
- //尝试分配输出字符串
- if (!fail) out=(char*)cJSON_malloc(len);
- if (!out) fail=1;
-
- //处理失败
- if (fail)
- {
- //释放二级、一级指针所对应申请的内存空间
- for (i=0;i<numentries;i++) {if (names[i]) cJSON_free(names[i]);if (entries[i]) cJSON_free(entries[i]);}
- cJSON_free(names);cJSON_free(entries);
- return 0;
- }
-
- //组成并输出
- *out='{';ptr=out+1;if (fmt)*ptr++='n';*ptr=0;
- for (i=0;i<numentries;i++)
- {
- if (fmt) for (j=0;j<depth;j++) *ptr++='t';
- tmplen=strlen(names[i]);memcpy(ptr,names[i],tmplen);ptr+=tmplen;
- *ptr++=':';if (fmt) *ptr++='t';
- strcpy(ptr,entries[i]);ptr+=strlen(entries[i]);
- if (i!=numentries-1) *ptr++=',';
- if (fmt) *ptr++='n';*ptr=0;
- cJSON_free(names[i]);cJSON_free(entries[i]);
- }
-
- cJSON_free(names);cJSON_free(entries);
- if (fmt) for (i=0;i<depth-1;i++) *ptr++='t';
- *ptr++='}';*ptr++=0;
-
- //printf("out:n%sn", ptr);
- }
- return out;
- }
该函数内部的 while 部分代码会遍历该对象(通过后继指针)下的每个字节点,直到 child = child->next中child指针为空的时候,才停止,并将所有的这些已经字符串的数据组合为一个完整的对象。
print_array API内部实现
- //将数组呈现为文本
- static char *print_array(cJSON *item,int depth,int fmt,printbuffer *p)
- {
- char **entries;
- char *out=0,*ptr,*ret;int len=5;
- cJSON *child=item->child;
- int numentries=0,i=0,fail=0;
- size_t tmplen=0;
-
- //数组中有多少项?
- while (child) numentries++,child=child->next;
- //处理当数组为空的时候. 直接格式化为: out = |[|]|0|
- if (!numentries)
- {
- if (p) out=ensure(p,3);
- else out=(char*)cJSON_malloc(3);
- if (out) strcpy(out,"[]");
- return out;
- }
-
- if (p)
- {
- /* Compose the output array. */
- i=p->offset;
- ptr=ensure(p,1);if (!ptr) return 0; *ptr='['; p->offset++;
- child=item->child;
- while (child && !fail)
- {
- print_value(child,depth+1,fmt,p);
- p->offset=update(p);
- 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;}
- child=child->next;
- }
- ptr=ensure(p,2);if (!ptr) return 0; *ptr++=']';*ptr=0;
- out=(p->buffer)+i;
- }
- else
- {
- printf("entries[%d]n", numentries);
- //申请/分配一个数组来保存其中的每个值
- entries=(char**)cJSON_malloc(numentries*sizeof(char*));
- if (!entries) return 0;
- memset(entries,0,numentries*sizeof(char*));
- //检索所有结果.
- child=item->child;
- while (child && !fail)
- {
- ret=print_value(child,depth+1,fmt,0);
- entries[i++]=ret;
- if (ret) len+=strlen(ret)+2+(fmt?1:0); else fail=1;
- child=child->next;
- }
-
- //如果没有失败,尝试malloc输出字符串
- if (!fail) out=(char*)cJSON_malloc(len);
- //如果malloc失败,则失败并结束
- if (!out) fail=1;
-
- //处理失败,则释放申请的cJSON内存空间
- if (fail)
- {
- for (i=0;i<numentries;i++) if (entries[i]) cJSON_free(entries[i]);
- cJSON_free(entries);
- return 0;
- }
-
- //组合并输出数组
- *out='[';
- ptr=out+1;*ptr=0;
- for (i=0;i<numentries;i++)
- {
- tmplen=strlen(entries[i]);memcpy(ptr,entries[i],tmplen);ptr+=tmplen;
- if (i!=numentries-1) {*ptr++=',';if(fmt)*ptr++=' ';*ptr=0;}
- cJSON_free(entries[i]);
- }
- cJSON_free(entries);
- *ptr++=']';*ptr++=0;
- }
- printf("out:n%sn", out);
- return out;
- }
对于该函数接口,内部最为重要的核心部分便是如下 while 循环内部的内容。JSON数组中可以嵌套满足任意数量的符合JSON规则的数据类型(包括:数组、布尔、空值、字符串、对象等),每次循环遍历的时候,都会调用 print_value 函数,而该函数内部会对每个cJSON数据对象进行 type判断,循环递归进行该操作,直到达到该数组的最后一个子节点为止。
其详细格式化流程参考图16。需再次强调的是:JSON格式中其 Key永远是字符串,Value 可以是满足JSON规则的任务基本类型/复杂嵌套类型。
图16 对象/数组内部嵌套对象/数组格式化流程
主要 cJSON_Parse 函数。其声明如下:
- // cJSON.h文件定义
- extern cJSON *cJSON_Parse(const char *value);
- // cJSON.c函数功能实现
- cJSON *cJSON_Parse(const char *value) {return cJSON_ParseWithOpts(value,0,0);}
- /
- cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated)
- {
- const char *end=0;
- cJSON *c=cJSON_New_Item();
- ep=0;
- if (!c) return 0; //memory失败
-
- end=parse_value(c,skip(value));
- //解析失败,ep设置相应的错误码信息
- if (!end) {cJSON_Delete(c);return 0;}
-
- //如果需要以空结尾的JSON而不附加垃圾,则跳过并且然后检查是否有空结束符
- if (require_null_terminated) {end=skip(end);if (*end) {cJSON_Delete(c);ep=end;return 0;}}
- if (return_parse_end) *return_parse_end=end;
- return c;
- }
另外三个相关联的函数分别是 cJSON_GetArraySize、cJSON_GetArrayItem和cJSON_GetObjectItem。将格式化后的JSON转换为cJSON数据结构是将cJSON格式化文本的一个逆过程。其相关接口和实现比较简单,因此这里不进行说明。
- //返回数组(或对象)中的项数
- extern int cJSON_GetArraySize(cJSON *array);
- //从数组"array"中检索项目号"item",如果不成功,则返回NULL
- extern cJSON *cJSON_GetArrayItem(cJSON *array,int item);
- //从对象中获取项"string".不区分大小写
- extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string);
本文来自个人博客 ·【此处不归牛顿管】
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。