開源項目--cJSON6--JSON生成器

什麼是JSON生成器?

JSON生成器負責把樹形數據結構轉化爲JSON文本,這個過程又稱爲字符串化(stringify)。

頭文件

生成器的API:

char* lept_stringify(const lept_value* v, size_t* length);

在實現JSON解析的時候,我們加入了一個動態堆棧,用於存儲臨時的解析結果。而在JSON生成器中,也要存儲生成的結果,所以最簡單的再利用該數據結構,作爲輸出緩衝區。

因爲我們已經寫過JSON解析器,所以生成器的寫法就照着解析器寫就可以。

#ifndef LEPT_PARSE_STRINGIFY_INIT_SIZE
#define LEPT_PARSE_STRINGIFY_INIT_SIZE 256
#endif

//int lept_stringify(const lept_value* v, char** json, size_t* length){
//      lept_context c;
//      int ret;
//      assert(v != NULL);
//      assert(json != NULL);
//      c.stack = (char*)malloc(c.size = LEPT_PARSE_STRINGIFY_INIT_SIZE);
//      c.top = 0;
//      if (LEPT_PARSE_OK != (ret = lept_stringify_value(&c, v))){
//             free(c.stack);
//             *json = NULL;
//             return ret;
//      }
//      if (length)
//             *length = c.top;
//      PUTC(&c, '\0');
//      *json = c.stack;
//      return LEPT_PARSE_OK;
//}

char* lept_stringify(const lept_value* v, size_t* length)
{
       lept_context c;
       assert(v != NULL);
       c.stack = (char*)malloc(c.size = LEPT_PARSE_STRINGIFY_INIT_SIZE);
       c.top = 0;
       lept_stringify_value(&c, v);
       if (length)
              *length = c.top;
       PUTC(&c, '\0');
       return c.stack;
}

測試代碼

測試代碼編寫的思路:將一個JSON進行解析生成字符串,然後利用這個字符串在生成JSON2,最後逐字符對比兩個JSON是否相同,這種測試稱爲“往返測試”。

#define TEST_ROUNDTRIP(json)\
        do {\
               lept_value v; \
               char* json2; \
               size_t length; \
               lept_init(&v); \
               EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json)); \
               json2 = lept_stringify(&v, &length); \
               EXPECT_EQ_STRING(json, json2, length); \
               lept_free(&v); \
               free(json2); \
        } while (0)

static void test_stringify_number() {
        TEST_ROUNDTRIP("0");
        TEST_ROUNDTRIP("-0");
        TEST_ROUNDTRIP("1");
        TEST_ROUNDTRIP("-1");
        TEST_ROUNDTRIP("1.5");
        TEST_ROUNDTRIP("-1.5");
        TEST_ROUNDTRIP("3.25");
        TEST_ROUNDTRIP("1e+20");
        TEST_ROUNDTRIP("1.234e+20");
        TEST_ROUNDTRIP("1.234e-20");

        TEST_ROUNDTRIP("1.0000000000000002"); /* the smallest number > 1 */
        TEST_ROUNDTRIP("4.9406564584124654e-324"); /* minimum denormal */
        TEST_ROUNDTRIP("-4.9406564584124654e-324");
        TEST_ROUNDTRIP("2.2250738585072009e-308");  /* Max subnormal double */
        TEST_ROUNDTRIP("-2.2250738585072009e-308");
        TEST_ROUNDTRIP("2.2250738585072014e-308");  /* Min normal positive double */
        TEST_ROUNDTRIP("-2.2250738585072014e-308");
        TEST_ROUNDTRIP("1.7976931348623157e+308");  /* Max double */
        TEST_ROUNDTRIP("-1.7976931348623157e+308");
}

static void test_stringify_string() {
        TEST_ROUNDTRIP("\"\"");
        TEST_ROUNDTRIP("\"Hello\"");
        TEST_ROUNDTRIP("\"Hello\\nWorld\"");
        TEST_ROUNDTRIP("\"\\\" \\\\ / \\b \\f \\n \\r \\t\"");
        TEST_ROUNDTRIP("\"Hello\\u0000World\"");
}

static void test_stringify_array() {
        TEST_ROUNDTRIP("[]");
        TEST_ROUNDTRIP("[null,false,true,123,\"abc\",[1,2,3]]");
}

static void test_stringify_object() {
        TEST_ROUNDTRIP("{}");
        TEST_ROUNDTRIP("{\"n\":null,\"f\":false,\"t\":true,\"i\":123,\"s\":\"abc\",\"a\":[1,2,3],\"o\":{\"1\":1,\"2\":2,\"3\":3}}");
}
static void test_stringify() {
        TEST_ROUNDTRIP("null");
        TEST_ROUNDTRIP("false");
        TEST_ROUNDTRIP("true");
        test_stringify_number();
        test_stringify_string();
        test_stringify_array();
        test_stringify_object();
}

函數實現

首先看看lept_parse_value函數的實現。

//解析的代碼
static int lept_parse_value(lept_context* c, lept_value* v) {
    switch (*c->json) {
        case 't':  return lept_parse_literal(c, v, "true", LEPT_TRUE);
        case 'f':  return lept_parse_literal(c, v, "false", LEPT_FALSE);
        case 'n':  return lept_parse_literal(c, v, "null", LEPT_NULL);
        default:   return lept_parse_number(c, v);//對於number的情況,可以這樣處理。
        case '\0': return LEPT_PARSE_EXPECT_VALUE;
    }
}

//仿寫生成的函數
#define PUTS(c, s, len)     memcpy(lept_context_push(c, len), s, len)
static int lept_stringify_value(lept_context* c, const lept_value* v) {
    size_t i;
    int ret;
    switch (v->type) {
        case LEPT_NULL:   PUTS(c, "null",  4); break;
        case LEPT_FALSE:  PUTS(c, "false", 5); break;
        case LEPT_TRUE:   PUTS(c, "true",  4); break;
        /* ... */
    }
    return LEPT_STRINGIFY_OK;
}
生成數字
case LEPT_NUMBER:
            {
                char buffer[32];
                int length = sprintf(buffer, "%.17g", v->u.n);
                PUTS(c, buffer, length);
            }
            break;

但這樣需要在 PUTS() 中做一次 memcpy(),實際上我們可以避免這次複製,只需要生成的時候直接寫進 c 裏的推棧,然後再按實際長度調查 c->top:


case LEPT_NUMBER:
            {
                char* buffer = lept_context_push(c, 32);
                int length = sprintf(buffer, "%.17g", v->u.n);
                c->top -= 32 - length;
                
            }
            break;

簡寫成:


case LEPT_NUMBER:
			c->top -= 32 - sprintf(lept_context_push(c, 32), "%.17g", v->u.n);
            break;
生成字符串
static void lept_stringify_string(lept_context* c, const char* s, size_t len) {
    size_t i;
    assert(s != NULL);
    PUTC(c, '"');
    for (i = 0; i < len; i++) {
        unsigned char ch = (unsigned char)s[i];
        switch (ch) {
            case '\"': PUTS(c, "\\\"", 2); break;
            case '\\': PUTS(c, "\\\\", 2); break;
            case '\b': PUTS(c, "\\b",  2); break;
            case '\f': PUTS(c, "\\f",  2); break;
            case '\n': PUTS(c, "\\n",  2); break;
            case '\r': PUTS(c, "\\r",  2); break;
            case '\t': PUTS(c, "\\t",  2); break;
            default:
                if (ch < 0x20) {
                    char buffer[7];
                    sprintf(buffer, "\\u%04X", ch);
                    PUTS(c, buffer, 6);
                }
                else
                    PUTC(c, s[i]);
        }
    }
    PUTC(c, '"');}

static void lept_stringify_value(lept_context* c, const lept_value* v) {
    switch (v->type) {
        /* ... */
        case LEPT_STRING: lept_stringify_string(c, v->u.s.s, v->u.s.len); break;
    }
}

優化 lept_stringify_string()

上面的 lept_stringify_string() 實現中,每次輸出一個字符/字符串,都要調用 lept_context_push()。如果我們使用一些性能剖測工具,也可能會發現這個函數消耗較多 CPU。

static void* lept_context_push(lept_context* c, size_t size) {
    void* ret;
    assert(size > 0);
    if (c->top + size >= c->size) { // (1)
        if (c->size == 0)
            c->size = LEPT_PARSE_STACK_INIT_SIZE;
        while (c->top + size >= c->size)
            c->size += c->size >> 1;  /* c->size * 1.5 */
        c->stack = (char*)realloc(c->stack, c->size);
    }
    ret = c->stack + c->top;       // (2)
    c->top += size;                // (3)
    return ret;                    // (4)
}

中間最花費時間的,應該會是 (1),需要計算而且作分支檢查。即使使用 內聯函數去減少函數調用的開銷,這個分支也無法避免。所以,一個優化的點子是,預先分配足夠的內存,每次加入字符就不用做這個檢查了。但多大的內存才足夠呢?我們可以看到,每個字符可生成最長的形式是 \u00XX,佔 6 個字符,再加上前後兩個雙引號,也就是共 len * 6 + 2 個輸出字符。那麼,使用 char* p = lept_context_push() 作一次分配後,便可以用 *p++ = c 去輸出字符了。最後,再按實際輸出量調整堆棧指針。另一個小優化點,是自行編寫十六進位輸出,避免了 printf() 內解析格式的開銷。

static void lept_stringify_string(lept_context* c, const char* s, size_t len) {
    static const char hex_digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
    size_t i, size;
    char* head, *p;
    assert(s != NULL);
    p = head = lept_context_push(c, size = len * 6 + 2); /* "\u00xx..." */
    *p++ = '"';
    for (i = 0; i < len; i++) {
        unsigned char ch = (unsigned char)s[i];
        switch (ch) {
            case '\"': *p++ = '\\'; *p++ = '\"'; break;
            case '\\': *p++ = '\\'; *p++ = '\\'; break;
            case '\b': *p++ = '\\'; *p++ = 'b';  break;
            case '\f': *p++ = '\\'; *p++ = 'f';  break;
            case '\n': *p++ = '\\'; *p++ = 'n';  break;
            case '\r': *p++ = '\\'; *p++ = 'r';  break;
            case '\t': *p++ = '\\'; *p++ = 't';  break;
            default:
                if (ch < 0x20) {
                    *p++ = '\\'; *p++ = 'u'; *p++ = '0'; *p++ = '0';
                    *p++ = hex_digits[ch >> 4];
                    *p++ = hex_digits[ch & 15];
                }
                else
                    *p++ = s[i];
        }
    }
    *p++ = '"';
    c->top -= size - (p - head);
}

要注意的是,很多優化都是有代價的。第一個優化採取空間換時間的策略,對於只含一個字符串的 JSON,很可能會分配多 6 倍內存;但對於正常含多個值的 JSON,多分配的內存可在之後的值所利用,不會造成太多浪費。而第二個優化的缺點,就是有稍增加了一點程序體積。也許有人會問,爲什麼 hex_digits 不用字符串字面量 “0123456789ABCDEF”?其實是可以的,但這會多浪費 1 個字節(實際因數據對齊可能會浪費 4 個或更多)。

生成數組和對象

生成數組也是非常簡單,只要輸出 [ 和 ],中間對逐個子值遞歸調用 lept_stringify_value()。只要注意在第一個元素後才加入 ,。而對象也僅是多了一個鍵和 :。


static void lept_stringify_value(lept_context* c, const lept_value* v) {
    size_t i;
    switch (v->type) {
        /* ... */
        case LEPT_ARRAY:
            PUTC(c, '[');
            for (i = 0; i < v->u.a.size; i++) {
                if (i > 0)
                    PUTC(c, ',');
                lept_stringify_value(c, &v->u.a.e[i]);
            }
            PUTC(c, ']');
            break;
        case LEPT_OBJECT:
            PUTC(c, '{');
            for (i = 0; i < v->u.o.size; i++) {
                if (i > 0)
                    PUTC(c, ',');
                lept_stringify_string(c, v->u.o.m[i].k, v->u.o.m[i].klen);
                PUTC(c, ':');
                lept_stringify_value(c, &v->u.o.m[i].v);
            }
            PUTC(c, '}');
            break;
        /* ... */
    }}

最終代碼

#ifndef LEPT_PARSE_STRINGIFY_INIT_SIZE
#define LEPT_PARSE_STRINGIFY_INIT_SIZE 256
#endif

#define PUTS(c, s, len) memcpy(lept_context_push(c,len),s,len)

static void lept_stringify_string(lept_context* c, const char* s, size_t len){
        static const char hex_digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', 
'9', 'A', 'B', 'C', 'D', 'E', 'F' };
        size_t i, size;
        char* head, *p;
        assert(s != NULL);
        p = head = lept_context_push(c, size = len * 6 + 2);//每個字符可生成最長的形式是\u00XX,佔 6 個字符,再加上前後兩個雙引號,也就是共 len * 6 + 2 個輸出字符
        *p++ = '"';
        //PUTC(c, '"');  //PUTC 函數每次都會調用lept_context_push函數,這個函數的if(c->top + size >= c->size) 這一句開銷比較大,需要計算而且作分支檢查。即使使用 C99 的inline 關鍵字(或使用宏)去減少函數調用的開銷,這個分支也無法避免;  寫成*p++比較好
        for (i = 0; i < len; i++){
               unsigned char ch = (unsigned char)s[i];
               switch (ch)
               {
               /*case '\"': PUTC(c, "\\\"", 2); break;
               case '\\': PUTC(c, "\\\\", 2); break;
               case '\b': PUTC(c, "\\b", 2); break;
               case '\f': PUTC(c, "\\f", 2); break;
               case '\n': PUTC(c, "\\n", 2); break;
               case '\r': PUTC(c, "\\r", 2); break;
               case '\t': PUTC(c, "\\t", 2); break;*/

               case '\"': *p++ = '\\'; *p++ = '\"'; break;
               case '\\': *p++ = '\\'; *p++ = '\\'; break;
               case '\b': *p++ = '\\'; *p++ = 'b';  break;
               case '\f': *p++ = '\\'; *p++ = 'f';  break;
               case '\n': *p++ = '\\'; *p++ = 'n';  break;
               case '\r': *p++ = '\\'; *p++ = 'r';  break;
               case '\t': *p++ = '\\'; *p++ = 't';  break;
               default:
                       if (ch < 0x20){
                              //char buffer[7];
                              //sprintf(buffer, "\\u%04X", ch);//其他少於 0x20 的字符需要轉義爲 \u00xx 形式。
                              //PUTS(c, buffer, 0);
                              *p++ = '\\'; *p++ = 'u'; *p++ = '0'; *p++ = '0';
                              *p++ = hex_digits[ch >> 4];
                              *p++ = hex_digits[ch & 15];

                      }
                       else
                              //PUTC(c, s[i]);
                              *p++ = s[i];
               }
        }
        //PUTC(c, '"');
        *p++ = '"';
        c->top -= size - (p - head);
}


static int lept_stringify_value(lept_context* c, const lept_value* v){
        size_t i;
        int ret;
        switch (v->type)
        {
        case LEPT_NULL: PUTS(c, "null", 4); break;
        case LEPT_FALSE: PUTS(c, "false", 5); break;
        case LEPT_TRUE: PUTS(c, "true", 4); break;
        case LEPT_NUMBER: 
        //{
        //           char* buffer = lept_context_push(c, 32);
        //            int length = sprintf(buffer, "%.17g", v->u.n);//sprintf把浮點數轉換爲文本
        //      c->top -= 32 - length;   
        //}
               c->top -= 32 - sprintf(lept_context_push(c, 32), "%.17g", v->u.n);
               break;
        case LEPT_STRING: lept_stringify_string(c, v->u.s.s, v->u.s.len); break;
        case LEPT_ARRAY:
               PUTC(c, '[');
               for (i = 0; i < v->u.a.size; i++){
                       if (i > 0)
                              PUTC(c, ',');
                       lept_stringify_value(c, &v->u.a.e[i]);
               }
               PUTC(c, ']');
               break;
        case LEPT_OBJECT:
               PUTC(c, '{');
               for (i = 0; i < v->u.o.size; i++){
                       if (i>0)
                              PUTC(c, ',');
                       lept_stringify_string(c, v->u.o.m[i].k, v->u.o.m[i].klen);
                       PUTC(c, ':');
                       lept_stringify_value(c, &v->u.o.m[i].v);
               }
               PUTC(c, '}');
               break;
        default:
               assert(0 && "invalid type");
        }
     

}

char* lept_stringify(const lept_value* v, size_t* length)
{
        lept_context c;
        assert(v != NULL);
        c.stack = (char*)malloc(c.size = LEPT_PARSE_STRINGIFY_INIT_SIZE);
        c.top = 0;
        lept_stringify_value(&c, v);
        if (length)
               *length = c.top;
        PUTC(&c, '\0');
        return c.stack;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章