cjson 源碼分析

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)題
  1. 定義的數(shù)據(jù)結(jié)構(gòu)是什么?
  2. 如何進(jìn)行內(nèi)存管理?
    2.1 如何創(chuàng)建結(jié)點(diǎn)
    2.2 如何刪除結(jié)點(diǎn)
  3. 如果將一個(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ì)象
  4. 對(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)
  5. 輸出和序列化

理解源碼,可以按照這個(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)如下:

image.png

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


image.png
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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容