cjson 源碼分析

cjson 的源碼大約1000行左右澄者,用C語言實現(xiàn)了一個json的解析器笆呆。c語言沒有字典或key-value這樣的數(shù)據結構,所以處理json需要自己定義數(shù)據結構來處理粱挡,想想都是一個很激動的事情赠幕。

cjson的官網 : http://www.json.org/
下載源碼 : https://sourceforge.net/projects/cjson/

另外,在源碼里面可以看到對json字符串的解析過程(其實本質是一個自動機)询筏,還有一個問題就是內存的管理榕堰,由于c的特性需要自己用戶管理內存(即自定義 malloc 和 free)。這幾天看完cjson嫌套,這個的確是一個很好的教程逆屡,來幫助你更好的理解底層,即使你平常不用c寫代碼踱讨。

cjson源碼里面試圖解決的幾個問題
  1. 定義的數(shù)據結構是什么魏蔗?
  2. 如何進行內存管理?
    2.1 如何創(chuàng)建結點
    2.2 如何刪除結點
  3. 如果將一個json類型的字符串轉解析成對應的數(shù)據結構痹筛?
    3.1 如何解析字符串
    3.2 如何解析數(shù)組
    3.3 如何解析數(shù)字
    3.4 如何解析嵌套的json對象
  4. 對json結點的操作
    4.1 添加結點
    4.2 刪除結點
    4.3 查找結點
    4.4 修改結點
  5. 輸出和序列化

理解源碼沫勿,可以按照這個來對照一個挨约,看看作者的思路是如何干的,如果是你产雹,你會怎么做诫惭。

1. 定義的數(shù)據結構
    struct cJSON *next,*prev;   /同一級的元素使用雙向列表儲存/
    struct cJSON *child;        /* 如果是個 object 或 array 的話,第一個兒子的指針 */
    int type;                   /* value 的類型 */
    char *valuestring;          /* 如果這個 value 是 字符串 的話蔓挖,字符串值 */
    int valueint;               /* 如果是數(shù)字的話夕土,整數(shù)值 */
    double valuedouble;         /* 如果是數(shù)字的話,浮點數(shù)值 */
    char *string;               /* 如果是對象的 key-value 元素的話瘟判, key 值 */

type字段的類型怨绣,表示的當前json結點的數(shù)據類型:

#define cJSON_False 0      
#define cJSON_True 1
#define cJSON_NULL 2
#define cJSON_Number 3      #數(shù)字,注意這個數(shù)字表示的并不是int類型的數(shù)組拷获,而是字符串類型的數(shù)字
#define cJSON_String 4      #字符串
#define cJSON_Array 5       #數(shù)組
#define cJSON_Object 6      #嵌套json的類型

如果是對象或者數(shù)組篮撑,采用的是雙向鏈表來實現(xiàn),鏈表中的每一個節(jié)點表示數(shù)組中的一個元素或者對象中的一個字段匆瓜。其中child表示頭結點赢笨,next、prev分別表示下一個節(jié)點和前一個節(jié)點驮吱。valuestring茧妒、valueint、valuedouble分別表示字符串左冬、整數(shù)桐筏、浮點數(shù)的字面量。

一個如下的json拇砰,如果用這個數(shù)據結構去表示的話梅忌,結構如下:

image.png

如果是一個json的嵌套,那么結構如下所示:


image.png
2. 內存管理

內存管理指的是創(chuàng)建一個JSON結點和刪除一個JSON結點除破,創(chuàng)建結點就是使用malloc內存分配铸鹰,刪除結點就判斷一下JSON的type,如果是基本類型就直接釋放皂岔,如果是數(shù)組或者對象就遞歸刪除(類似二叉樹)。

//創(chuàng)建結點
static cJSON *cJSON_New_Item(void) {
    cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON));
    if (node) memset(node,0,sizeof(cJSON));
    return node;
}

//刪除結點展姐,注意看遞歸刪除的部分
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解析的功能實現(xiàn)的是給出一個字符串躁垛,然后把它解析成一個cjson的數(shù)據結構的類型。

解析的核心代碼:

//根據字符串來判斷當前json結點的類型
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;
    }
    //字符串類型
    if (*value=='\"') {
        return parse_string(item,value);
    }
    //數(shù)字類型
    if (*value=='-' || (*value>='0' && *value<='9')) {
        return parse_number(item,value);
    }
    //數(shù)組類型
    if (*value=='[') {
        return parse_array(item,value);
    }
    //object類型
    if (*value=='{') {
        return parse_object(item,value);
    }
    ep=value;
    return 0;/* failure. */
}

對于不同的字符串圾笨,調用不同的解析函數(shù)教馆。貼一個比較復雜的解析函數(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)建一個子節(jié)點child
    item->child=child=cJSON_New_Item();
    if (!item->child) return 0;

    //子節(jié)點 解析字符串,執(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 在這里想表達的是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. 對json結點的操作

對結點的操作,我覺得可以抽象的理解為一棵樹(多叉樹),這樣對結點的操作基本上和樹對結點的操作是相似的悲敷。

4.1 添加結點
4.2 刪除結點
4.3 查找結點
4.4 修改結點

5. 輸出和序列化

cjson 的做法不是邊分析 json 邊輸出究恤, 而是預先將要輸?shù)膬热萑堪醋址嬖趦却嬷校?最后輸出整個字符串。所以后德,看代碼的時候部宿,可以順著這個思路去看。

PS

最近用寫論文空余的時間瓢湃,連著看了三個經典的開源項目理张,按代碼量從小到大,分別是webbench绵患,tinyhttp雾叭,cjson。感覺受益匪淺落蝙,從這些前輩的代碼里面可以快速的學到這門語言的最佳實踐织狐,解決問題的思路,coding的風格掘殴。

再有一點赚瘦,體會很深的一點是,這幾個開源的項目里面奏寨,大量的代碼都是為了考慮解決異常的情況起意,比如輸入異常,故意輸入錯誤如何處理病瞳;創(chuàng)建進程失敗揽咕,如何處理;創(chuàng)建管道出錯怎么辦套菜;內存分配失敗怎么辦等等亲善。這些繁雜的工程細節(jié)會讓人厭煩,但不得不承認逗柴,這是一個優(yōu)秀代碼的必須要有的東西蛹头。

如何在國外的網站下載源碼不方便,可以移步我的github:
與源碼相比戏溺,我在github里面的版本插入了很多printf來輸出渣蜗,應該可以更容易理解一點吧
https://github.com/zhaozhengcoder/rebuild-the-wheel/tree/master/cJSON/src

參考文獻 :

http://blog.csdn.net/yzhang6_10/article/details/51615089
http://www.reibang.com/p/838f69db2f71
http://github.tiankonguse.com/blog/2014/12/18/cjson-source.html

others

webbench的源碼:http://www.reibang.com/p/e65c7efc96aa
tinyhttp的源碼:http://www.reibang.com/p/18cfd6019296

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市旷祸,隨后出現(xiàn)的幾起案子耕拷,更是在濱河造成了極大的恐慌,老刑警劉巖托享,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骚烧,死亡現(xiàn)場離奇詭異浸赫,居然都是意外死亡,警方通過查閱死者的電腦和手機赃绊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門既峡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人凭戴,你說我怎么就攤上這事涧狮。” “怎么了么夫?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵者冤,是天一觀的道長。 經常有香客問我档痪,道長涉枫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任腐螟,我火速辦了婚禮愿汰,結果婚禮上,老公的妹妹穿的比我還像新娘乐纸。我一直安慰自己衬廷,他們只是感情好,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布汽绢。 她就那樣靜靜地躺著吗跋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪宁昭。 梳的紋絲不亂的頭發(fā)上跌宛,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天,我揣著相機與錄音积仗,去河邊找鬼疆拘。 笑死,一個胖子當著我的面吹牛寂曹,可吹牛的內容都是我干的哎迄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼隆圆,長吁一口氣:“原來是場噩夢啊……” “哼漱挚!你這毒婦竟也來了?” 一聲冷哼從身側響起匾灶,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎租漂,沒想到半個月后阶女,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體颊糜,經...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年秃踩,在試婚紗的時候發(fā)現(xiàn)自己被綠了衬鱼。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡憔杨,死狀恐怖鸟赫,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情消别,我是刑警寧澤抛蚤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站寻狂,受9級特大地震影響岁经,放射性物質發(fā)生泄漏。R本人自食惡果不足惜蛇券,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一缀壤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧纠亚,春花似錦塘慕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至啤誊,卻和暖如春岳瞭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蚊锹。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工瞳筏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人牡昆。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓姚炕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親丢烘。 傳聞我的和親對象是個殘疾皇子柱宦,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354