开源项目--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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UC/OS-II 是一个实时操作系统,而 cJSON 是一个轻量级的 JSON 解析器和生成器库。如果你想在 UC/OS-II 中使用 cJSON,你需要将 cJSON 的源代码添加到你的项目中,并进行相应的配置。 以下是在 UC/OS-II 中使用 cJSON 的一般步骤: 1. 将 cJSON 的源代码添加到你的 UC/OS-II 项目中。可以将 cJSON 的源文件(cJSON.c 和 cJSON.h)直接复制到你的项目目录中,或者将其放在一个单独的文件夹中,并将该文件夹添加到你的项目的 include 路径中。 2. 在你的 UC/OS-II 任务中包含 cJSON 头文件,并在任务初始化的地方进行 cJSON 的初始化。例如,你可以在任务初始化函数中调用 `cJSON_Init()` 函数来初始化 cJSON: ```c #include "cJSON.h" void YourTask(void *p_arg) { // 初始化 cJSON cJSON_Init(); // 其他任务代码... } ``` 3. 在你的任务中使用 cJSON 解析和生成 JSON 数据。你可以使用 cJSON 的 API 来解析和生成 JSON 数据。例如,使用 `cJSON_Parse()` 函数解析 JSON 字符串,使用 `cJSON_CreateObject()` 和 `cJSON_AddItemToObject()` 函数创建和添加 JSON 对象等。 以下是一个简单的例子,演示了如何使用 cJSON 解析和生成 JSON 数据: ```c #include "cJSON.h" void YourTask(void *p_arg) { cJSON *root = NULL; char *jsonStr = "{\"name\":\"John\",\"age\":30,\"city\":\"New York\"}"; // 解析 JSON 字符串 root = cJSON_Parse(jsonStr); if (root != NULL) { // 从 JSON 对象中获取字段值 cJSON *name = cJSON_GetObjectItem(root, "name"); cJSON *age = cJSON_GetObjectItem(root, "age"); cJSON *city = cJSON_GetObjectItem(root, "city"); if (name != NULL && age != NULL && city != NULL) { // 打印字段值 printf("Name: %s\n", name->valuestring); printf("Age: %d\n", age->valueint); printf("City: %s\n", city->valuestring); } // 释放 cJSON 对象 cJSON_Delete(root); } // 生成 JSON 对象 cJSON *newRoot = cJSON_CreateObject(); cJSON_AddStringToObject(newRoot, "name", "Alice"); cJSON_AddNumberToObject(newRoot, "age", 25); cJSON_AddStringToObject(newRoot, "city", "London"); // 将 JSON 对象转换为字符串 char *newJsonStr = cJSON_Print(newRoot); if (newJsonStr != NULL) { printf("New JSON string: %s\n", newJsonStr); free(newJsonStr); } // 释放 cJSON 对象 cJSON_Delete(newRoot); // 其他任务代码... } ``` 这只是一个简单的示例,你可以根据你的具体需求使用其他 cJSON 的 API 来解析和生成 JSON 数据。希望对你有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值