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所示:

图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      //对象

2. cJSON框架剖析

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

typedef struct cJSON {struct cJSON *next,*prev;   //next/prev允许您遍历数组/对象链.或者,使用use GetArraySize/GetArrayItem/GetObjectItemstruct 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);

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;

图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;
}

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


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

     此例较简单,因为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

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

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报文


2.4 创建一个对象类型的JSON, 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 - 2assert(pSubObj);cJSON_AddItemToObject(pSubObj, "ReadingBook", cJSON_CreateString("C++"));cJSON *pSubArr = cJSON_CreateArray();  //pSubArr - 3assert(pSubArr);cJSON *pDataObj = cJSON_CreateObject();   //pDataObj - 4cJSON_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


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

     其最终的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;}

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


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

     当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绝对=0names[i]=str=print_string_ptr(child->string,0);//print_vlaue非常核心的APIentries[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 对象/数组内部嵌套对象/数组格式化流程


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

主要 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);


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

windows c++ cjson 使用_cJSON源码剖析相关推荐

  1. Mongoose源码剖析:Introduction and Installation

    引言 要剖析Mongoose的源码,首先你得知道它的一些基本情况和特性.并去使用它.本文就是介绍Mongoose是个什么东西?及如何安装和使用?这里假设你知道什么web服务器软件.web服务器使用什么 ...

  2. 菜鸟nginx源码剖析

    菜鸟nginx源码剖析 配置与部署篇(一) 手把手配置nginx "I love you"  TCMalloc 对MYSQL 性能 优化的分析 菜鸟nginx源码剖析系列文章解读 ...

  3. java实现gdal栅格矢量化,《GDAL源码剖析与开发指南》一一1.5 GDAL源码目录

    本节书摘来自异步社区出版社<GDAL源码剖析与开发指南>一书中的第1章,第1.5节,作者:李民录 更多章节内容可以访问云栖社区"异步社区"公众号查看. 1.5 GDAL ...

  4. 源码剖析 Netty 服务启动 NIO

    如果这个文章看不懂的话 , 建议反复阅读 Netty 与 Reactor 开篇立意.引用网友好的建议.看源码要针对性的看,最佳实践就是,带着明确的目的去看源码.抓主要问题,放弃小问题.主要的逻辑理解了 ...

  5. Python envoy 模块源码剖析

    Kenneth Reitz 是公认的这个世界上 Python 代码写得最好的人之一.抱着学习的心态,我阅读了 Reitz 写的 envoy 模块的源码,将笔记记录如下. 介绍 和 requests 模 ...

  6. GDAL源码剖析(一)

    前言:一直在使用和研究GDAL的相关东西,发现网上对GDAL的内容倒是不少,但是很少有系统的介绍说明,以及内部的一些结构说明,基于这些原因,将本人的一些粗浅的理解放在此处,形成一个系列,暂时名为< ...

  7. Chrome源码剖析、上--多线程模型、进程通信、进程模型

    Chrome源码剖析.上 原著:duguguiyu. 整理:July. 时间:二零一一年四月二日. 出处:http://blog.csdn.net/v_JULY_v. 说明:此Chrome源码剖析很大 ...

  8. Chrome源码剖析 上--多线程模型 进程通信 进程模型

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! Chro ...

  9. CHROME源码剖析 上《转》

    转自:http://www.blogjava.net/xiaomage234/archive/2012/02/16/370122.html 原著:duguguiyu. 整理:July. 时间:二零一一 ...

最新文章

  1. 高质量的友谊总是发生在两个优秀的独立人格之间
  2. Nature:最强癌基因找到了,居然不在染色体上 | 华人领衔最新研究
  3. 【转】Windows7 下安装 JDK 7 时版本冲突问题解决
  4. javascript小数四舍五入
  5. Hadoop-2.2.0学习之三YARN简介
  6. Windows Phone的网络连接策略
  7. RMAN Restore, Recovery
  8. 使用structure101分析软件包的依赖关系
  9. Spring Data JPA 从入门到精通~查询方法的创建
  10. RecycleView拖拽、侧滑删除
  11. 解压rootfs.img根文件系统
  12. linux中级之防火墙的数据传输过程
  13. 视觉SLAM十四讲第五讲
  14. 微信语音转mp3 php,微信语音amr转mp3
  15. Matlab中tic和toc用法
  16. 时间片,从多任务系统说起
  17. 剑指offer题目及答案
  18. Linux中静态库和动态库(共享库)的区别
  19. FPGA控制ADS1256的ADC采集(三)
  20. 错误:类 xxx 是公共的, 应在名为 xxx.java 的文件中声明

热门文章

  1. secuteCRT的使用
  2. 实现Android的消息通知栏
  3. Linq To Sql, 为何继承就这么费劲?
  4. 混乱的MVC,.NET非要MVC不可么?
  5. zabbix-将业务机器加入到监控中
  6. python网络-多线程(22)
  7. 【算法笔记】B1015 德才论
  8. oracle习题-emp表查询练习
  9. CAS 单点登录模块学习
  10. Cordova Android 禁用长按选中功能