cJSON库源码分析

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

cJSON库源码分析
本⽂采⽤以下协议进⾏授权: ,转载请注明作者及出处。

cJSON是⼀个超轻巧,携带⽅便,单⽂件,简单的可以作为ANSI-C标准的Json格式解析库。

那什么是Json格式?这⾥照搬度娘百科的说法:
Json(JavaScript Object Notation) 是⼀种轻量级的数据交换格式。

它基于JavaScript(Standard ECMA-262 3rd Edition – December 1999)的⼀个⼦集。

JSON采⽤完全独⽴于语⾔的⽂本格式,但是也使⽤了类似于C语⾔家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。

这些特性使JSON成为理想的数据交换语⾔。

易于⼈阅读和编写,同时也易于机器解析和⽣成。

其实简单说,Json就是⼀种信息交换格式,⽽cJSON其实就是对Json格式的字符串进⾏构建和解析的⼀个C语⾔函数库。

可以在以下地址下载到cJSON的源代码:
__MACOSX⽬录是提供给Mac OS的源码,我的机器运⾏的是Fedora 18,所以选择另外⼀个⽬录即可。

简单的阅读下README⽂件,先学习cJSON库的使⽤⽅法。

若是连库都还不会使⽤,分析源码就⽆从谈起了。

通过简单的了解,我们得知cJSON库实际上只有cJSON.c和cJSON.h两个⽂件组成,绝对轻量级。

不过,代码风格貌似有点⾮主流,先⽤indent格式化⼀下代码吧。

我个⼈喜欢K&R风格的代码,使⽤的indent
命令⾏参数如下:
1indent - bad - bli 0 - ce - kr - nsob -- space - after - if -- space - after - while -- space - after - for -- use - tabs - i8
格式化之后,代码结构看起来清晰多了。

那么,从何处下⼿来分析呢?打开代码⽂件逐⾏阅读么?当然不是了,有main函数的程序⼤都是从main函数开始分析,那么没有main函数的纯函数库呢?那就⾃⼰写main函数呗。

cJSON作为Json格式的解析库,其主要功能⽆⾮就是构建和解析Json格式了,我们先写⼀个构建Json格式字符串的程序,尽可能的使其⽤到的类型多⼀点(事实上README⽂件⾥提供了不错的⽰例代码,我们直接借鉴⼀下吧)。

代码如下:
1 2 3 4 5 6 7#include <stdlib.h>
#include "cJSON.h"
int main ( int argc , char * argv [ ] ) {
cJSON * root , * fmt ;
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
root = cJSON_CreateObject ( ) ;
cJSON_AddStringToObject ( root , "name" , "Jack (\"Bee\") Nimble" ) ; fmt = cJSON_CreateObject ( ) ;
cJSON_AddItemToObject ( root , "format" , fmt ) ;
cJSON_AddStringToObject ( fmt , "type" , "rect" ) ;
cJSON_AddNumberToObject ( fmt , "width" , 1920 ) ;
cJSON_AddFalseToObject ( fmt , "interlace" ) ;
char * result = cJSON_Print ( root ) ;
puts ( result ) ;
free ( result ) ;
cJSON_Delete ( root ) ;
return EXIT_SUCCESS ;
}
编译运⾏后 ( 编译时注意要链接数学库,参数⾏要加 -lm)
,运⾏结果如下:
1 2 3 4 5 6 7 8 "name" : "Jack (\"Bee\") Nimble" ,
"format" : {
"type" : "rect" ,
"width" : 1920 ,
"interlace" : false
}
}
打开cJSON.h这个头⽂件,我们可以看到每⼀个节点,实际上都是由cJSON
这个结构体来描述的:
1 2 3 4 5 6 struct cJSON * next , * prev ; struct cJSON * child ;
int type ;
6 7 8 9 10 11 12
char * valuestring ;
int valueint ;
double valuedouble ;
char * string ;
} cJSON ;
结合这个结构体和上⾯相关API的调⽤,其实我们⼤概可以猜测出cJSON对于Json格式的描述和处理的⽅法了:
每⼀个cJSON结构都描述了⼀项”键-值”对的数据,其中next和prev指针显然是指向同级前后的cJSON结构,⽽child指针⾃然是指向孩⼦节点的cJSON结
构。

type类型显然是为了区分值的类型⽽设置的,在cJSON.h
⽂件⼀开始就定义了这些类型的值:
1 2 3 4 5 6 7 8
#define cJSON_False 0
#define cJSON_True 1
#define cJSON_NULL 2
#define cJSON_Number 3
#define cJSON_String 4
#define cJSON_Array 5
#define cJSON_Object 6
很显然通过检测这⾥的type字段,就很容易知道该节点的类型以及其实际存储数据的字段了。

其它的字段是什么意思呢?cJSON.h⽂件⾥的注释说的很
明⽩了,valueint,valuedouble以及valuestring保存的是相应的值,string存放的是本字段的名字。

接下来分析程序的执⾏过程,编译参数加上-g,使⽤gdb调试程序,画出整个构造过程的函数调⽤图。

具体的调试过程就不细说了,我捡⼀些关键点说说:
调试过程中,我们发现 cJSON_AddStringToObject() 等其实是宏定义,本质上调⽤的都是 cJSON_AddItemToObject() 函数,在cJSON.h⽂件中可以看
到如下定义:
1 2 3 4 5 6#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))
另外 cJSON_CreateNull() 等函数都是调⽤ cJSON_New_Item() 函数申请到初始化为0的空间构造相关的节点信息。

构造过程中的函数调⽤图如下:
构造的Json字符串最终在内存中形成的结构如下图所⽰:
构造过程相对来说⽐较简单,数组类型这⾥没有涉及到,但是分析起来也很简单。

我们最后调⽤ cJSON_Print() 函数⽣成这个结构所对应的字符串。

⽣成说起来容易,遍历起整个结构并进⾏字符串格式控制却⽐较繁琐。

这⾥相关的代
码还有递归清理这个内存结构的函数不再赘述,有兴趣的同学请⾃⾏研究。

构造的过程我们就说到这⾥,明天我们研究下解析的过程。

昨天简单的分析了⼀下cJSON对Json格式的构造过程,今天仔细读了读README⽂件,发现README其实说的已经很详细了。

重复造轮⼦就重复造轮⼦吧,今天我们再⼀起分析解析的过程。

继续⽤之前构造的Json格式来进⾏解析,之前分析构造函数的时候,我们只是简单的分析了⼏个cJSON结构的构造过程,并没有涉及到各种类型的数组等构造。

因为我觉得理解了⼀般的构造过程,更复杂的类型⾃⼰再简单看看源码,画画图就很容易理解。

学习⼀个事物⼀定要先抓住主线,先掌握⼀个事物最常⽤的那50%,其他的边边⾓⾓完全可以留给实践去零敲碎打(孟岩语)。

闲话打住,先上⼀段解析使⽤的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26#include <stdlib.h>
#include "cJSON.h"
int main ( int argc , char * argv [ ] )
{
char * text = "{\"name\": \"Jack (\\\"Bee\\\") Nimble\", "
"\"format\": {\"type\": \"rect\", "
"\"width\": 1920, \"interlace\": false}}" ;
cJSON * root = cJSON_Parse ( text ) ;
if ( ! root ) {
printf ( "Error before: [%s]\n" , cJSON_GetErrorPtr ( ) ) ;
return EXIT_FAILURE ;
}
char * out = cJSON_Print ( root ) ;
printf ( "text:\n%s\n\n" , out ) ;
free ( out ) ;
char * name = cJSON_GetObjectItem ( root , "name" ) -> valuestring ; printf ( "name : %s\n" , name ) ;
27 28 29 30 31cJSON * format = cJSON_GetObjectItem ( root , "format" ) ;
int width = cJSON_GetObjectItem ( format , "width" ) -> valueint ; printf ( "width : %d\n" , width ) ;
cJSON_Delete ( root ) ;
return EXIT_SUCCESS ;
}
程序运⾏输出:
1 2 3 4 5 6 7 8 9 10 11 12
text :
{
"name" : "Jack (\"Bee\") Nimble" ,
"format" : {
"type" : "rect" ,
"width" : 1920 ,
"interlace" : false
}
}
name : Jack ( "Bee" ) Nimble
width : 1920
从这段代码中可以看到,解析过程就 cJSON_Parse() ⼀个接⼝,调⽤成功返回cJSON结构体的指针,错误返回NULL,此时调⽤ cJSON_GetErrorPtr()
可以得要错误原因的描述字符串。

查看 cJSON_GetErrorPtr() 的源码可以得知,其实错误信息就保存在全局字符串指针ep⾥。

关键就是对 cJSON_Parse() 过程的分析了,我们带参数-g重新编译代码并下断点开始调试跟踪。

⾸先 cJSON_Parse() 调⽤ cJSON_New_Item() 申请⼀个新的cJSON节点,然后使⽤函数对输⼊字符串进⾏解析(中间使⽤了 skip() 函数来跳过空格和换⾏符等字符)。

parse_value() 函数对输⼊字符串进⾏匹配和解析,检测输⼊数据的类型并调⽤ parse_string() 、 parse_number() 、 parse_array() 、 parse_object() 等函数进⾏解析,然后返回结束的位置。

函数调⽤的关系如下图:
这些函数之间相互调⽤,传递待解析的字符串直到结束或者遇见错误便返回,最后会构建出⼀个和之前结构⼀样的Json内存结构来,解析的过程就完成了。

检索过程很简单 cJSON_GetObjectItem() 函数负责进⾏某个对象的⾃成员的名字⽐对和指针的返回。

不过要注意这⾥采⽤了 cJSON_strcasecmp() 这个⽆视⼤⼩写的字符串⽐较函数,因为Json格式的键值对的名称不区分⼤⼩写。

这样cJSON库的整个构建和解析过程的主⼲内容就总结出来了,剩下的边边⾓⾓可以在这个主线分析结束之后再继续下去,⽐如Json格式化,解析出来的内存结构复制,从这个内存结构解析出字符串以及这个内存结构的递归删除等等留给⼤家⾃⼰进⾏吧。

P.S. cJSON_InitHooks() 这个函数不过是cJSON允许⽤户使⽤其它的内存申请和释放函数罢了(默认是malloc和free),另外啰嗦⼀下,这个接⼝也可以⽤来检测内存泄露。

只要实现malloc和free的包装函数,在其中统计和打印内存申请释放操作就可以了。

若⽆特别声明,本站⽂章皆为原创,转载时烦请注明:转载⾃。

相关文档
最新文档