开源项目cJSON具体实现6(对象的解析)

JSON 对象语法

本章实现的是JSON对象。JSON对象的实现和JSON数组的实现很是相似,我们可以对比着来看。

JSON数组JSON对象
在这里插入图片描述在这里插入图片描述
JSON数组是由JSON值value组成JSON对象是由成员对象member组成,成员对象是键值对
JSON数组是 [] 构成JSON对象是 {} 构成
一个数组可以包含零至多个值,
而这些值可以字符,数字,也可以是数组;
值与值之间以逗号分隔,
例如 []、[1,2,true]、[[1,2],[3,4],“abc”]
都是合法的数组。但注意
JSON数组 不接受末端额外的逗号,例如 [1,2,] 是不合法的。
JSON对象由成员对象组成,成员对象由键值对组成,其中键为 JSON字符串(string),值是任何JSON值(value),中间由冒号分割(%x3A)。

JSON对象语法。

JSON-text = ws value ws
value = null / false / true / number / string / array / object

object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D
member = string ws %x3A ws value

解释:

  • %x7B 是左大括号 {
  • %x2C 是逗号 ,
  • %x7D 是右大括号 }
  • ws 是空白字符
  • %x3A 是冒号
头文件

在 ECMA-404 标准中,并没有规定对象中每个成员的键一定要唯一的,也没有规定是否需要维持成员的次序。为了简单起见,我们选择用动态数组来实现JSON对象,对于对象,既然我们采用了动态数组的方案,那么每个对象就是成员的数组:

typedef struct lept_value lept_value;
typedef struct lept_member lept_member;

struct lept_value {
    union {
        struct { lept_member* m; size_t size; }o;
        struct { lept_value* e; size_t size; }a;
        struct { char* s; size_t len; }s;
        double n;
    }u;
    lept_type type;
};

/*动态数组表示JSON对象(键值对)*/
struct lept_member {
    char* k;  /* 键(字符串) */
    size_t klen;   /* 字符串的长度,因为可能包含空字符\u0000 */
    lept_value v;           /* 值 */
};

添加错误码:

enum {
    /* ... */
    LEPT_PARSE_MISS_KEY,      //没有键
    LEPT_PARSE_MISS_COLON,      //没有冒号
    LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET
};

同样的,添加API

size_t lept_get_object_size(const lept_value* v);
const char* lept_get_object_key(conat lept_calue* v, size_t index);
size_t lept_get_object_key_length(const lept_value* v, size_t index);
lept_value* lept_get_object_value(const lept_value* v, size_t index);

实现API函数:

size_t lept_get_object_size(const lept_value* v)
{
        assert(v != NULL && v->type == LEPT_OBJECT);
        return v->u.o.size;
}

const char* lept_get_object_key(const lept_value* v, size_t index)
{
        assert(v != NULL && v->type == LEPT_OBJECT);
        assert(index < v->u.o.size);
        return v->u.o.m[index].k;
}

size_t lept_get_object_key_length(const lept_value* v, size_t index)
{
        assert(v != NULL && v->type == LEPT_OBJECT);
        assert(index < v->u.o.size);
        return v->u.o.m[index].klen;
}

lept_value* lept_get_object_value(const lept_value* v, size_t index)
{
        assert(v != NULL && v->type == LEPT_OBJECT);
        assert(index < v->u.o.size);
       return &v->u.o.m[index].v;
}
测试代码
static void test_parse_object() {
    lept_value v;
    size_t i;

    lept_init(&v);
    EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, " { } "));
    EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(&v));
    EXPECT_EQ_SIZE_T(0, lept_get_object_size(&v));
    lept_free(&v);

    lept_init(&v);
    EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v,
        " { "
        "\"n\" : null , "
        "\"f\" : false , "
        "\"t\" : true , "
        "\"i\" : 123 , "
        "\"s\" : \"abc\", "
        "\"a\" : [ 1, 2, 3 ],"
        "\"o\" : { \"1\" : 1, \"2\" : 2, \"3\" : 3 }"
        " } "
    ));
    EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(&v));
    EXPECT_EQ_SIZE_T(7, lept_get_object_size(&v));
    EXPECT_EQ_STRING("n", lept_get_object_key(&v, 0), lept_get_object_key_length(&v, 0));
    EXPECT_EQ_INT(LEPT_NULL,   lept_get_type(lept_get_object_value(&v, 0)));
    EXPECT_EQ_STRING("f", lept_get_object_key(&v, 1), lept_get_object_key_length(&v, 1));
    EXPECT_EQ_INT(LEPT_FALSE,  lept_get_type(lept_get_object_value(&v, 1)));
    EXPECT_EQ_STRING("t", lept_get_object_key(&v, 2), lept_get_object_key_length(&v, 2));
    EXPECT_EQ_INT(LEPT_TRUE,   lept_get_type(lept_get_object_value(&v, 2)));
    EXPECT_EQ_STRING("i", lept_get_object_key(&v, 3), lept_get_object_key_length(&v, 3));
    EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_object_value(&v, 3)));
    EXPECT_EQ_DOUBLE(123.0, lept_get_number(lept_get_object_value(&v, 3)));
    EXPECT_EQ_STRING("s", lept_get_object_key(&v, 4), lept_get_object_key_length(&v, 4));
    EXPECT_EQ_INT(LEPT_STRING, lept_get_type(lept_get_object_value(&v, 4)));
    EXPECT_EQ_STRING("abc", lept_get_string(lept_get_object_value(&v, 4)), 
	lept_get_string_length(lept_get_object_value(&v, 4)));
    EXPECT_EQ_STRING("a", lept_get_object_key(&v, 5), lept_get_object_key_length(&v, 5));
    EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(lept_get_object_value(&v, 5)));
    EXPECT_EQ_SIZE_T(3, lept_get_array_size(lept_get_object_value(&v, 5)));

    for (i = 0; i < 3; i++) {
        lept_value* e = lept_get_array_element(lept_get_object_value(&v, 5), i);
        EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(e));
        EXPECT_EQ_DOUBLE(i + 1.0, lept_get_number(e));
    }
    EXPECT_EQ_STRING("o", lept_get_object_key(&v, 6), lept_get_object_key_length(&v, 6));
    {
        lept_value* o = lept_get_object_value(&v, 6);
        EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(o));
        for (i = 0; i < 3; i++) {
            lept_value* ov = lept_get_object_value(o, i);
            EXPECT_TRUE('1' + i == lept_get_object_key(o, i)[0]);
            EXPECT_EQ_SIZE_T(1, lept_get_object_key_length(o, i));
            EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(ov));
            EXPECT_EQ_DOUBLE(i + 1.0, lept_get_number(ov));
        }
    }

    lept_free(&v);
}

static void test_parse() {
        /*   ...    */
        test_parse_object();
}

错误码的测试函数

static void test_parse_miss_key() {
        TEST_ERROR(LEPT_PARSE_MISS_KEY, "{:1,");
        TEST_ERROR(LEPT_PARSE_MISS_KEY, "{1:1,");
        TEST_ERROR(LEPT_PARSE_MISS_KEY, "{true:1,");
        TEST_ERROR(LEPT_PARSE_MISS_KEY, "{false:1,");
        TEST_ERROR(LEPT_PARSE_MISS_KEY, "{null:1,");
        TEST_ERROR(LEPT_PARSE_MISS_KEY, "{[]:1,");
        TEST_ERROR(LEPT_PARSE_MISS_KEY, "{{}:1,");
        TEST_ERROR(LEPT_PARSE_MISS_KEY, "{\"a\":1,");
}

static void test_parse_miss_colon() {
        TEST_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\"}");
        TEST_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\",\"b\"}");
}

static void test_parse_miss_comma_or_curly_bracket() {
        TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1");
        TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1]");
        TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1 \"b\"");
        TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":{}");
}

static void test_parse() {
        /*   ...    */
        test_parse_object();
        test_parse_miss_key();
        test_parse_miss_colon();
        test_parse_miss_comma_or_curly_bracket();
}
函数实现

在函数是先前,我们先来讨论一个问题—重构

代码重构 code refactoring是指在不改变软件外在行为时,修改代码以改进结构。代码重构十分依赖于单元测试,因为我们是通过单元测试去维护代码的正确性。有了足够的单元测试,我们可以放胆去重构,尝试并评估不同的改进方式,找到合乎心意而且能通过单元测试的改动,我们才提交它。

我们知道,成员的键也是一个 JSON 字符串,然而,我们不使用 lept_value 存储键,因为这样会浪费了当中 type 这个无用的字段。由于 lept_parse_string() 是直接地把解析的结果写进一个 lept_value,所以我们先用「提取方法 」的重构方式,把解析 JSON 字符串及写入 lept_value 分拆成两部分。

先看原来的 lept_parse_string()函数:

static int lept_parse_string(lept_context* c, lept_value* v) {
    size_t head = c->top, len;
    unsigned u, u2;
    const char* p;
    EXPECT(c, '\"');
    p = c->json;
    for (;;) {
        char ch = *p++;
        switch (ch) {
            case '\"':
                len = c->top - head;
                lept_set_string(v, (const char*)lept_context_pop(c, len), len);
                c->json = p;
                return LEPT_PARSE_OK;
            case '\\':
                switch (*p++) {
                    case '\"': PUTC(c, '\"'); break;
                    case '\\': PUTC(c, '\\'); break;
                    case '/':  PUTC(c, '/' ); break;
                    case 'b':  PUTC(c, '\b'); break;
                    case 'f':  PUTC(c, '\f'); break;
                    case 'n':  PUTC(c, '\n'); break;
                    case 'r':  PUTC(c, '\r'); break;
                    case 't':  PUTC(c, '\t'); break;
                    case 'u':
                        if (!(p = lept_parse_hex4(p, &u)))
                            STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX);
                        if (u >= 0xD800 && u <= 0xDBFF) { /* surrogate pair */
                            if (*p++ != '\\')
                                STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE);
                            if (*p++ != 'u')
                                STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE);
                            if (!(p = lept_parse_hex4(p, &u2)))
                                STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX);
                            if (u2 < 0xDC00 || u2 > 0xDFFF)
                                STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE);
                            u = (((u - 0xD800) << 10) | (u2 - 0xDC00)) + 0x10000;
                        }
                        lept_encode_utf8(c, u);
                        break;
                    default:
                        STRING_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE);
                }
                break;
            case '\0':
                STRING_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK);
            default:
                if ((unsigned char)ch < 0x20)
                    STRING_ERROR(LEPT_PARSE_INVALID_STRING_CHAR);
                PUTC(c, ch);
        }
    }
}

这段函数有两个作用:1. 是解析字符串 2. 是把字符串保存在 lept_value里,因为JSON对象在解析时,前面键的部分也就是字符串是保存在lept_member里面,所以我们把这个函数拆开看。

/* 解析 JSON 字符串,把结果写入 str 和 len */
/* str 指向 c->stack 中的元素,需要在 c->stack  */
static int lept_parse_string_raw(lept_context* c, char** str, size_t* len)
{
        size_t head = c->top;
        unsigned u, u2;
        const char* p;
        EXPECT(c, '\"');
        p = c->json;
        for (;;) {
            char ch = *p++;
            switch (ch) {
                case '\"':
                    *len = c->top - head;//注意,这里得用*len,而不是len,主要是因为要改变的参数里的值
                              *str = lept_context_pop(c, *len);
                              c->json = p;
                    return LEPT_PARSE_OK;
                case '\\':
                    switch (*p++) {
                        case '\"': PUTC(c, '\"'); break;
                        case '\\': PUTC(c, '\\'); break;
                        case '/':  PUTC(c, '/' ); break;
                        case 'b':  PUTC(c, '\b'); break;
                        case 'f':  PUTC(c, '\f'); break;
                        case 'n':  PUTC(c, '\n'); break;
                        case 'r':  PUTC(c, '\r'); break;
                        case 't':  PUTC(c, '\t'); break;
                        case 'u':
                            if (!(p = lept_parse_hex4(p, &u)))
                                STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX);
                            if (u >= 0xD800 && u <= 0xDBFF) { /* surrogate pair */
                                if (*p++ != '\\')
                                    STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE);
                                if (*p++ != 'u')
                                    STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE);
                                if (!(p = lept_parse_hex4(p, &u2)))
                                    STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX);
                                if (u2 < 0xDC00 || u2 > 0xDFFF)
                                    STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE);
                                u = (((u - 0xD800) << 10) | (u2 - 0xDC00)) + 0x10000;
                            }
                            lept_encode_utf8(c, u);
                            break;
                        default:
                            STRING_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE);
                    }
                    break;
                case '\0':
                    STRING_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK);
                default:
                    if ((unsigned char)ch < 0x20)
                        STRING_ERROR(LEPT_PARSE_INVALID_STRING_CHAR);
                    PUTC(c, ch);
            }
        }
}

static int lept_parse_string(lept_context* c, lept_value* v)
{
        int ret;
        char* s;
        size_t len;
        if (LEPT_PARSE_OK == (ret = lept_parse_string_raw(c, &s, &len))){
               lept_set_string(v, s, len);
        }
        return ret;
}

了解上面的内容后,我们可以开始下面的内容,解析对象函数的编写。

注意,我们在解析数组的时候,是把当前元素以lept_value压入栈中,而在这里,我们则是以lept_member压入:

static int lept_parse_object(lept_context* c, lept_value* v)
{
        size_t size,i;
        int ret;
        lept_member m;
        EXPECT(c, '{');
        lept_parse_whitespace(c);
        if (*c->json == '}'){
               c->json++;
               v->type = LEPT_OBJECT;
               v->u.o.m = NULL;
               v->u.o.size = 0;
               return LEPT_PARSE_OK;
        }
        m.k = NULL;
        size = 0;
        for (;;){
               char* str;
               lept_init(&m.v);
               /*解析 键*/
               if (*c->json != '"') {  //不是字符串
                       ret = LEPT_PARSE_MISS_KEY;
                       break;
               }
               if ((ret = lept_parse_string_raw(c, &str, &m.klen)) != LEPT_PARSE_OK){  
						//解析字符串错误
                       break;
               }
               //否则解析字符串正确,将字符串保存在m.k里面
               memcpy(m.k = (char*)malloc(m.klen + 1), str, m.klen);
               m.k[m.klen] = '\0';
               
               //解析冒号
               lept_parse_whitespace(c);
               if (*c->json != ':') {  //不是冒号
                       ret = LEPT_PARSE_MISS_COLON;
                       break;
               }
               c->json++;
               lept_parse_whitespace(c);

               //解析 值, 值是任意的JSON值,把结果写进m.v里面
               if ((ret = lept_parse_value(c, &m.v)) != LEPT_PARSE_OK){  //解析错误
                       break;
               }                      
               //解析成功,把m里面的结果写入c里面
               memcpy(lept_context_push(c, sizeof(lept_member)), &m, sizeof(lept_member));
               size++;
               m.k = NULL;//如果之前缺乏冒号,或是这里解析值失败,在函数返回前我们要释放m.k。如果我们成功地解析整个成员,那么就要把 m.k 设为空指针,其意义是说明该键的字符串的拥有权已转移至栈,之后如遇到错误,我们不会重覆释放栈里成员的键和这个临时成员的键。

               //解析逗号
               lept_parse_whitespace(c);
               if (*c->json == ',') {
                       c->json++;
                       lept_parse_whitespace(c);
               }
               else if (*c->json == '}') {
                       //size_t s = sizeof(lept_member)* size;
                       c->json++;
                       v->type = LEPT_OBJECT;
                       v->u.o.size = size;
                       size *= sizeof(lept_member);
                       memcpy(v->u.o.m = (lept_member*)malloc(size), lept_context_pop(c, size), size);
                       return LEPT_PARSE_OK;
               }
               else {
                       ret = LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET;
                       break;
               }
        }

        //for循环遇到错误时:释放临时key字符串及栈上的成员
        free(m.k);
        for (i = 0; i < size; i++){
               lept_member* m = (lept_member*)lept_context_pop(c, sizeof(lept_member));
               free(m->k);
               lept_free(&m->v);
        }
        v->type = LEPT_NULL;
        return ret;
}

//不要忘记修改lept_parse_value函数
static int lept_parse_value(lept_context* c, lept_value* v) {
                /*   ....*/
               case '{':  return lept_parse_object(c, v);
}
以下是一个模糊测试用例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include "cJSON.h" #define MAX_JSON_LENGTH 1000 int main() { srand(time(NULL)); char json[MAX_JSON_LENGTH]; int i, j; for (i = 0; i < 10; i++) { memset(json, 0, MAX_JSON_LENGTH); int num_objects = rand() % 10 + 1; strcat(json, "{"); for (j = 0; j < num_objects; j++) { // 随机生成键名 int key_length = rand() % 10 + 1; char key[key_length + 1]; int k; for (k = 0; k < key_length; k++) { key[k] = rand() % 26 + 'a'; } key[key_length] = '\0'; strcat(json, "\""); strcat(json, key); strcat(json, "\":"); // 随机生成值 int value_type = rand() % 5; switch (value_type) { case 0: // null strcat(json, "null"); break; case 1: // bool strcat(json, rand() % 2 == 0 ? "true" : "false"); break; case 2: // number strcat(json, "12345"); break; case 3: // string strcat(json, "\"hello world\""); break; case 4: // array strcat(json, "["); int num_items = rand() % 10 + 1; int l; for (l = 0; l < num_items; l++) { int item_type = rand() % 4; switch (item_type) { case 0: // null strcat(json, "null"); break; case 1: // bool strcat(json, rand() % 2 == 0 ? "true" : "false"); break; case 2: // number strcat(json, "12345"); break; case 3: // string strcat(json, "\"hello world\""); break; } if (l != num_items - 1) { strcat(json, ","); } } strcat(json, "]"); break; } if (j != num_objects - 1) { strcat(json, ","); } } strcat(json, "}"); printf("JSON string: %s\n", json); cJSON *root = cJSON_Parse(json); if (root == NULL) { printf("Error parsing JSON string: %s\n", cJSON_GetErrorPtr()); } else { printf("JSON object successfully parsed!\n"); cJSON_Delete(root); } } return 0; } ``` 该测试用例随机生成一个 JSON 字符串,并尝试将其解析为 cJSON 对象具体而言,它随机生成一个对象对象包含随机数量的键值对,每个键值对的键名和值也是随机生成的。值的类型包括 null、bool、number、string 和 array,其 array 的元素也是随机生成的。最终,它将生成的 JSON 字符串打印到控制台,并输出解析结果或错误信息。由于是随机生成的测试用例,因此可以测试 cjson 项目的 json_parse 函数对各种不同形式的 JSON 字符串的解析能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值