天天看点

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),如下图所示:

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

▲ 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/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报文,网络发送,不同进程通信

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

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

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

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

▲ 解析格式化后的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;      
cJSON源码剖析,此文带你彻底掌握cJSON内部原理

▲ 创建基本类型(空值)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%s\n", pBuf);  cJSON_Delete(p);  free(pBuf);  return 0;}      

打印结果如图4所示

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

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

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

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

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

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

▲ 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%s\n", serialPcBuf);  cJSON_Delete(pRootArr);  free(serialPcBuf);      

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

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

▲  数组类型的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%s\n", serialBuf);  cJSON_Delete(pRootArr); //释放cJSON申请的内存空间  free(serialPcBuf);      

打印结果如下:

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

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

{  "name": "lixiaogang5",  "sex": 0,  "hobby": {    "ReadingBook": "C++",    "info": [      {        "B1": "C++ Primer",        "B2": "C++ 沉思录",        "B3": "深入理解C++11"      }    ]  }}      

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

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

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

数组和对象的深层嵌套:cJSON不支持嵌套太深的数组和对象,因为这会导致堆栈溢出。为了防止这种CJSON_NESTING_LIMIT情况,默认情况下,cJSON将深度限制为1000,但是可以在编译时进行更改。分析:cJSON数据结构中,同时有3个指针,分别是 prev、next和child。设计3个指针有何用处?什么时候用到 child,什么时候用到prev和next?

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

▲  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所示。

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

▲  cJSON报文解析相关API

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

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

cJSON源码剖析,此文带你彻底掌握cJSON内部原理
/////////////////// 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%s\n", serialBuf);  cJSON_Delete(pRoot);  free(serialBuf);      

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

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

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

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

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

▲  格式化JSON内部逻辑

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

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

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

▲  格式化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时候,这里循环只会走一次。得到结果如下所示。

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

第二步,初始化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;  }      
cJSON源码剖析,此文带你彻底掌握cJSON内部原理

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

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

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是通过二级指针来动态申请内存空间进行分配值的,如下两图所示。

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

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

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

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

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

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

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

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

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

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

  • print_object API内部实现
//将对象呈现为文本(item: JSON  . depth-0  . fmt-1. p-是否缓存空间) 0-1-0static 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%s\n", ptr);  }  return out;  }      

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

cJSON源码剖析,此文带你彻底掌握cJSON内部原理
  • 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%s\n", out);  return out;  }      

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

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

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

cJSON源码剖析,此文带你彻底掌握cJSON内部原理

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

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;}      
//返回数组(或对象)中的项数extern int    cJSON_GetArraySize(cJSON *array);//从数组"array"中检索项目号"item",如果不成功,则返回NULLextern cJSON *cJSON_GetArrayItem(cJSON *array,int item);//从对象中获取项"string".不区分大小写extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string);