从零开始的 JSON 库教程(五):解析数组

173 阅读3分钟

代码地址:github.com/WangHao-nlp…

第五节主要内容是解析数组,难点是如何循环递归解析数组里的内容,因为数组里的元素也都是json对象,也可能是数组;其次是如何存储数组元素。

leptjson.h

json对象中引入数组部分,包括一个json对象数组和size。

//typedef struct lept_value lept_value;   
//由于 lept_value 内使用了自身类型的指针,我们必须前向声明(forward declare)此类型。
// VS中不加似乎也可以
struct lept_value {
    union {
        struct { lept_value* e; size_t size; } a;
        struct { char* s; size_t len; } s;
        double n;
    }u;
    lept_type type;
};

新的解析结果,解析函数返回值

enum {
    LEPT_PARSE_OK = 0,
    LEPT_PARSE_EXPECT_VALUE,
    LEPT_PARSE_INVALID_VALUE,
    LEPT_PARSE_ROOT_NOT_SINGULAR,
    LEPT_PARSE_NUMBER_TOO_BIG,
    LEPT_PARSE_MISS_QUOTATION_MARK,
    LEPT_PARSE_INVALID_STRING_ESCAPE,
    LEPT_PARSE_INVALID_STRING_CHAR,
    LEPT_PARSE_INVALID_UNICODE_HEX,
    LEPT_PARSE_INVALID_UNICODE_SURROGATE,
    LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET // 缺少逗号或者方括号
};

leptjson.cpp

static int lept_parse_array(lept_context* c, lept_value* v) {
    size_t i, size = 0;
    int ret;
    EXPECT(c, '[');  // 识别到方括号
    lept_parse_whitespace(c);  // 去除空值符号
    if (*c->json == ']') {    // 如果识别到右方括号,直接解析结束,数组指针指向空
        c->json++;
        v->type = LEPT_ARRAY;
        v->u.a.size = 0;
        v->u.a.e = NULL;
        return LEPT_PARSE_OK;  
    }
    for (;;) {   // 挨个解析数组元素(可能是任何json对象)
        lept_value e;  // 重新初始化个json对象用来存储解析时候的临时变量
        lept_init(&e);
        if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) {  // 解析元素中的对象失败
            break;
            // 不能直接返回ret,要free后return,防止内存泄露
        }
        //printf("************%d\n", sizeof(lept_value));
        //printf("************%d\n", sizeof(lept_value::u));
        //printf("************%d\n", sizeof(lept_value::u.n));
        //printf("************%d\n", sizeof(lept_value::u.a));
        //printf("************%d\n", sizeof(lept_value::u.s));
        //printf("************%d\n", sizeof(lept_value::u.s.len));
        //printf("************%d\n", sizeof(lept_value::u.s.s));
        //printf("************%d\n\n", sizeof(lept_value::type));

        // 识别成功,则将临时变量存进json语句的stack中
        // 尽管每个json对象的种类可能不同,但由于按最大需要的内存进行存储,所以不会发生冲突
        // 比如数字只需要8字节(变量+类型);但字符串和数组都需要16字节(指针+大小+类型)
        // 但存储变量也用16字节,所以不需要考虑存储不同json类型时候的问题
        // 都是直接存16个字节的指针
        memcpy(lept_context_push(c, sizeof(lept_value)), &e, sizeof(lept_value));  // 任意类型均可
        size++;
        lept_parse_whitespace(c);
        if (*c->json == ',') {   // 遇到逗号, 继续解析
            c->json++;
            lept_parse_whitespace(c);
        }
        else if (*c->json==']') {  // 解析结束
            c->json++;
            v->type = LEPT_ARRAY;
            v->u.a.size = size;
            size *= sizeof(lept_value); 
            // 解析结束后,将json语句中的stack存进json对象中数组的指针里
            memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c, size), size);
            //printf("%d\n", lept_get_type(lept_get_array_element(v, 1)));
            return LEPT_PARSE_OK;
        }
        else {
            // 解析完一个元素后,缺少逗号和中括号
            ret = LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET;
            break;
        }
    }
    for (i = 0; i < size; i++)
        // 依次释放json对象内存
        lept_free((lept_value*)lept_context_pop(c, sizeof(lept_value)));
    return ret;
}

json语句中数组解析到json语句的栈,再从栈到json数组对象的指针,最后从指针里取对象
(这种指针操作我服气,太牛了)
向context指向的栈存数据

static void* lept_context_push(lept_context* c, size_t size) {
    void* ret;
    assert(size > 0);
    if (c->top + size >= c->size) {
        if (c->size == 0) {
                c->size = LEPT_PARSE_STACK_INIT_SIZE;
        }
        while (c->top + size >= c->size) {
                c->size += c->size >> 1;  
        }
        c->stack = (char*)realloc(c->stack, c->size);
    }
    ret = c->stack + c->top;  
    c->top += size;
    return ret;
}
// 存json对象到json语句中的栈中

//个人理解:不知道对不对
// 这种存法,可以避免push时候,重新开辟栈空间,指针变成悬挂指针。
// 不用先lept_value* e = lept_context_push(c, sizeof(lept_value))
// lept_parse_value(c, &e)时候,e可能会被改变

memcpy(lept_context_push(c, sizeof(lept_value)), &e, sizeof(lept_value));  // 任意类型均可

// 从栈中取指针到json对象
memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c, size), size);
// 从后往前依次释放json值内存
for (i = 0; i < size; i++)
    lept_free((lept_value*)lept_context_pop(c, sizeof(lept_value)));
    
// 按照index从json数组对象中取json对象
lept_value* lept_get_array_element(const lept_value*v,size_t index) {
    assert(v != NULL && v->type == LEPT_ARRAY);
    assert(index < v->u.a.size);
    return &v->u.a.e[index];
}

test.cpp

无效值

TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "[1,]"); // 不能有逗号
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "[\"a\", nul]"); // 最后一个值解析失败

缺少逗号、括号

TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1");
TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1}");
TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1 2");
TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[[]");

解析结果

教程5.png