cjson 的源碼大約1000行左右,用C語(yǔ)言實(shí)現(xiàn)了一個(gè)json的解析器。c語(yǔ)言沒(méi)有字典或key-value這樣的數(shù)據(jù)結(jié)構(gòu),所以處理json需要自己定義數(shù)據(jù)結(jié)構(gòu)來(lái)處理,想想都是一個(gè)很激動(dòng)的事情。
cjson的官網(wǎng) : http://www.json.org/
下載源碼 : https://sourceforge.net/projects/cjson/
另外,在源碼里面可以看到對(duì)json字符串的解析過(guò)程(其實(shí)本質(zhì)是一個(gè)自動(dòng)機(jī)),還有一個(gè)問(wèn)題就是內(nèi)存的管理,由于c的特性需要自己用戶管理內(nèi)存(即自定義 malloc 和 free)。這幾天看完cjson,這個(gè)的確是一個(gè)很好的教程,來(lái)幫助你更好的理解底層,即使你平常不用c寫(xiě)代碼。
cjson源碼里面試圖解決的幾個(gè)問(wèn)題
- 定義的數(shù)據(jù)結(jié)構(gòu)是什么?
- 如何進(jìn)行內(nèi)存管理?
2.1 如何創(chuàng)建結(jié)點(diǎn)
2.2 如何刪除結(jié)點(diǎn) - 如果將一個(gè)json類(lèi)型的字符串轉(zhuǎn)解析成對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)?
3.1 如何解析字符串
3.2 如何解析數(shù)組
3.3 如何解析數(shù)字
3.4 如何解析嵌套的json對(duì)象 - 對(duì)json結(jié)點(diǎn)的操作
4.1 添加結(jié)點(diǎn)
4.2 刪除結(jié)點(diǎn)
4.3 查找結(jié)點(diǎn)
4.4 修改結(jié)點(diǎn) - 輸出和序列化
理解源碼,可以按照這個(gè)來(lái)對(duì)照一個(gè),看看作者的思路是如何干的,如果是你,你會(huì)怎么做。
1. 定義的數(shù)據(jù)結(jié)構(gòu)
struct cJSON *next,*prev; /同一級(jí)的元素使用雙向列表儲(chǔ)存/
struct cJSON *child; /* 如果是個(gè) object 或 array 的話,第一個(gè)兒子的指針 */
int type; /* value 的類(lèi)型 */
char *valuestring; /* 如果這個(gè) value 是 字符串 的話,字符串值 */
int valueint; /* 如果是數(shù)字的話,整數(shù)值 */
double valuedouble; /* 如果是數(shù)字的話,浮點(diǎn)數(shù)值 */
char *string; /* 如果是對(duì)象的 key-value 元素的話, key 值 */
type字段的類(lèi)型,表示的當(dāng)前json結(jié)點(diǎn)的數(shù)據(jù)類(lèi)型:
#define cJSON_False 0
#define cJSON_True 1
#define cJSON_NULL 2
#define cJSON_Number 3 #數(shù)字,注意這個(gè)數(shù)字表示的并不是int類(lèi)型的數(shù)組,而是字符串類(lèi)型的數(shù)字
#define cJSON_String 4 #字符串
#define cJSON_Array 5 #數(shù)組
#define cJSON_Object 6 #嵌套json的類(lèi)型
如果是對(duì)象或者數(shù)組,采用的是雙向鏈表來(lái)實(shí)現(xiàn),鏈表中的每一個(gè)節(jié)點(diǎn)表示數(shù)組中的一個(gè)元素或者對(duì)象中的一個(gè)字段。其中child表示頭結(jié)點(diǎn),next、prev分別表示下一個(gè)節(jié)點(diǎn)和前一個(gè)節(jié)點(diǎn)。valuestring、valueint、valuedouble分別表示字符串、整數(shù)、浮點(diǎn)數(shù)的字面量。
一個(gè)如下的json,如果用這個(gè)數(shù)據(jù)結(jié)構(gòu)去表示的話,結(jié)構(gòu)如下:

如果是一個(gè)json的嵌套,那么結(jié)構(gòu)如下所示:

2. 內(nèi)存管理
內(nèi)存管理指的是創(chuàng)建一個(gè)JSON結(jié)點(diǎn)和刪除一個(gè)JSON結(jié)點(diǎn),創(chuàng)建結(jié)點(diǎn)就是使用malloc內(nèi)存分配,刪除結(jié)點(diǎn)就判斷一下JSON的type,如果是基本類(lèi)型就直接釋放,如果是數(shù)組或者對(duì)象就遞歸刪除(類(lèi)似二叉樹(shù))。
//創(chuàng)建結(jié)點(diǎn)
static cJSON *cJSON_New_Item(void) {
cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON));
if (node) memset(node,0,sizeof(cJSON));
return node;
}
//刪除結(jié)點(diǎn),注意看遞歸刪除的部分
void cJSON_Delete(cJSON *c) {
cJSON *next;
while (c) {
next=c->next;
if (!(c->type&cJSON_IsReference) && c->child) cJSON_Delete(c->child);
if (!(c->type&cJSON_IsReference) && c->valuestring) cJSON_free(c->valuestring);
if (c->string) cJSON_free(c->string);
cJSON_free(c);
c=next;
}
}
3. json解析
json解析的功能實(shí)現(xiàn)的是給出一個(gè)字符串,然后把它解析成一個(gè)cjson的數(shù)據(jù)結(jié)構(gòu)的類(lèi)型。
解析的核心代碼:
//根據(jù)字符串來(lái)判斷當(dāng)前json結(jié)點(diǎn)的類(lèi)型
static const char *parse_value(cJSON *item,const char *value) {
if (!value)return 0;/* Fail on null. */
if (!strncmp(value,"null",4)) {
item->type=cJSON_NULL;
return value+4;
}
if (!strncmp(value,"false",5)) {
item->type=cJSON_False;
return value+5;
}
if (!strncmp(value,"true",4)) {
item->type=cJSON_True;
item->valueint=1;
return value+4;
}
//字符串類(lèi)型
if (*value=='\"') {
return parse_string(item,value);
}
//數(shù)字類(lèi)型
if (*value=='-' || (*value>='0' && *value<='9')) {
return parse_number(item,value);
}
//數(shù)組類(lèi)型
if (*value=='[') {
return parse_array(item,value);
}
//object類(lèi)型
if (*value=='{') {
return parse_object(item,value);
}
ep=value;
return 0;/* failure. */
}
對(duì)于不同的字符串,調(diào)用不同的解析函數(shù)。貼一個(gè)比較復(fù)雜的解析函數(shù),就是解析object,分析如下:
static const char *parse_object(cJSON *item,const char *value)
{
printf("********* parse_object \n");
cJSON *child;
if (*value!='{') {ep=value;return 0;} /* not an object! */
item->type=cJSON_Object;
value=skip(value+1);
if (*value=='}') return value+1; /* empty array. */
//給item 創(chuàng)建一個(gè)子節(jié)點(diǎn)child
item->child=child=cJSON_New_Item();
if (!item->child) return 0;
//子節(jié)點(diǎn) 解析字符串,執(zhí)行之后,child->type=cjson_string;child->valuestring="name" ,parse_string的返回值=":\"zhao\",\"age\":18" 是剩下的字符串
value=skip(parse_string(child,skip(value)));
if (!value) return 0;
// child->string 在這里想表達(dá)的是json里面的key
child->string=child->valuestring;
child->valuestring=0;
if (*value!=':') {ep=value;return 0;} /* fail! */
// 繼續(xù)解析字符串里面的value的部分
value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */
if (!value) return 0;
while (*value==',')
{
cJSON *new_item;
if (!(new_item=cJSON_New_Item())) return 0; /* memory fail */
child->next=new_item;
new_item->prev=child;
child=new_item;
value=skip(parse_string(child,skip(value+1)));
if (!value) return 0;
child->string=child->valuestring;
child->valuestring=0;
if (*value!=':') {ep=value;return 0;} /* fail! */
value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */
if (!value) return 0;
}
if (*value=='}') return value+1; /* end of array */
ep=value;return 0; /* malformed. */
}
4. 對(duì)json結(jié)點(diǎn)的操作
對(duì)結(jié)點(diǎn)的操作,我覺(jué)得可以抽象的理解為一棵樹(shù)(多叉樹(shù)),這樣對(duì)結(jié)點(diǎn)的操作基本上和樹(shù)對(duì)結(jié)點(diǎn)的操作是相似的。
4.1 添加結(jié)點(diǎn)
4.2 刪除結(jié)點(diǎn)
4.3 查找結(jié)點(diǎn)
4.4 修改結(jié)點(diǎn)
5. 輸出和序列化
cjson 的做法不是邊分析 json 邊輸出, 而是預(yù)先將要輸?shù)膬?nèi)容全部按字符串存在內(nèi)存中, 最后輸出整個(gè)字符串。所以,看代碼的時(shí)候,可以順著這個(gè)思路去看。
PS
最近用寫(xiě)論文空余的時(shí)間,連著看了三個(gè)經(jīng)典的開(kāi)源項(xiàng)目,按代碼量從小到大,分別是webbench,tinyhttp,cjson。感覺(jué)受益匪淺,從這些前輩的代碼里面可以快速的學(xué)到這門(mén)語(yǔ)言的最佳實(shí)踐,解決問(wèn)題的思路,coding的風(fēng)格。
再有一點(diǎn),體會(huì)很深的一點(diǎn)是,這幾個(gè)開(kāi)源的項(xiàng)目里面,大量的代碼都是為了考慮解決異常的情況,比如輸入異常,故意輸入錯(cuò)誤如何處理;創(chuàng)建進(jìn)程失敗,如何處理;創(chuàng)建管道出錯(cuò)怎么辦;內(nèi)存分配失敗怎么辦等等。這些繁雜的工程細(xì)節(jié)會(huì)讓人厭煩,但不得不承認(rèn),這是一個(gè)優(yōu)秀代碼的必須要有的東西。
如何在國(guó)外的網(wǎng)站下載源碼不方便,可以移步我的github:
與源碼相比,我在github里面的版本插入了很多printf來(lái)輸出,應(yīng)該可以更容易理解一點(diǎn)吧
https://github.com/zhaozhengcoder/rebuild-the-wheel/tree/master/cJSON/src
參考文獻(xiàn) :
http://blog.csdn.net/yzhang6_10/article/details/51615089
http://www.itdecent.cn/p/838f69db2f71
http://github.tiankonguse.com/blog/2014/12/18/cjson-source.html
others
webbench的源碼:http://www.itdecent.cn/p/e65c7efc96aa
tinyhttp的源碼:http://www.itdecent.cn/p/18cfd6019296