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

图片

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

图片

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

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

图片

▲ 解析格式化后的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;
复制代码

图片

▲ 创建基本类型(空值)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所示

图片

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

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

图片

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

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

图片

▲ 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.

图片

▲ 数组类型的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 - 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%s\n", 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官网有如下这段描述:

图片

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

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

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

图片

▲ 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报文解析相关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%s\n", serialBuf);
  cJSON_Delete(pRoot);
  free(serialBuf);
复制代码

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

图片

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

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

图片

▲ 格式化JSON内部逻辑

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

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

图片

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

图片

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

图片

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

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

图片

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

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

图片

▲ 循环遍历并格式化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%s\n", 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%s\n", out);
  return out;  
}
复制代码

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

图片

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

图片

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

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);
复制代码

图片

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享