首页 > 其他分享 >Json-Tutorial05 数组解析

Json-Tutorial05 数组解析

时间:2023-01-09 11:46:55浏览次数:37  
标签:get Tutorial05 value parse Json lept 解析 LEPT size

前言

本节将要学习的是第一种复合类型的解析:数组。具体的解析规则在Tutorial中已经有了,概括下简单的思想就是遇到[符号之后挨个调用lept_parse_value来解析数组的每一个元素,当然每次遇到逗号就要将已经解析的那个元素进栈,当遇到]符号时,栈中所有元素一起出栈,保存到数组Json Value中。特别需要注意的是解析失败时的内存泄露问题。

代码设计

1. 编写 test_parse_array() 单元测试

lept_parse_array大致框架已经为我们搭建好的情况下,我们只需要为其写两个单测。由于数组元素个数、类型都是不确定的,所以不能用一个宏就搞定。这里的关键在于使用lept_get_array_element将数组元素一个个取出来,挨个对其进行值、类型的校验。

static void test_parse_array() {
    lept_value v;
    lept_init(&v);
    EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ ]"));
    EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v));
    EXPECT_EQ_SIZE_T(0, lept_get_array_size(&v));
    lept_free(&v);

    lept_init(&v);
    EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ null , false , true , 123 , \"abc\" ]"));
    EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v));
    EXPECT_EQ_SIZE_T(5, lept_get_array_size(&v));
    EXPECT_EQ_INT(LEPT_NULL, lept_get_type(lept_get_array_element(&v, 0)));
    EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(lept_get_array_element(&v, 1)));
    EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(lept_get_array_element(&v, 2)));
    EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_array_element(&v, 3)));
    EXPECT_EQ_DOUBLE(123.0, lept_get_number(lept_get_array_element(&v, 3)));
    EXPECT_EQ_STRING("abc", lept_get_string(lept_get_array_element(&v, 4)), 3);
    lept_free(&v);

    lept_init(&v);
    size_t i;
    EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ [ ] , [ 0 ] , [ 0 , 1 ] , [ 0 , 1 , 2 ] ]"));
    EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v));
    EXPECT_EQ_SIZE_T(4, lept_get_array_size(&v));
    for (i = 0; i < 4; i++) {
        lept_value* sub_v = lept_get_array_element(&v, i);
        EXPECT_EQ_SIZE_T(i, lept_get_array_size(sub_v));
        int j;
        for (j = 0; j < i; j++) {
            EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_array_element(sub_v, j)));
            EXPECT_EQ_DOUBLE((double)j, lept_get_number(lept_get_array_element(sub_v, j)));
        }
    }

    lept_free(&v);
}

2. lept_parse_array() 里加入空白字符处理

按现时的 lept_parse_array() 的编写方式,需要加入 3 个 lept_parse_whitespace() 调用,分别是解析 [ 之后,元素之后,以及 , 之后:

static int lept_parse_array(lept_context* c, lept_value* v) {
    /* ... */
    EXPECT(c, '[');
    lept_parse_whitespace(c);
    /* ... */
    for (;;) {
        /* ... */
        if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK)
            return ret;
        /* ... */
        lept_parse_whitespace(c);
        if (*c->json == ',') {
            c->json++;
            lept_parse_whitespace(c);
        }
        /* ... */
    }
}

3. 内存泄漏

我们在遇到]解析完成后,会为解析出来的数组使用mallocmemcpy为数组的Json Value分配可变内存。那么当一个单测用例结束时,我们应该将这部分内存释放,于是顺理成章地,这部分逻辑应该实现在lept_free里面。

void lept_free(lept_value* v) {
    assert(v != NULL);
    size_t i;
    if (v->type == LEPT_STRING)
        free(v->u.s.s);
    else if (v->type == LEPT_ARRAY) {
        for (i = 0; i < v->u.a.size; i++) {
            lept_free(&v->u.a.e[i]);
        }
        free(v->u.a.e);
    }
    v->type = LEPT_NULL;
}

这里需要把v中的元素挨个递归调用lept_free释放内存,因为数组元素也可以是一个数组,是一种嵌套关系

4. 解析错误时的缓冲区临时值处理

lept_parse_array中,有两个地方可能会导致解析失败:

  • lept_parse_value解析单个值
  • 除了数值、,]以外的符号

以下是我一开始的初版实现,仅仅在上述的两个分支内加了lept_context_pop函数,想将栈内的字符弹出。但是后来发现这个函数仅仅只是改了栈顶指针,并没有实际调用free

static int lept_parse_array(lept_context* c, lept_value* v) {
    size_t 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 (;;) {
        lept_value e;
        lept_init(&e);
        if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) {
            lept_context_pop(c, size * sizeof(lept_value));
            return ret;
        }
        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);
            memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c, size), size);
            return LEPT_PARSE_OK;
        }
        else {
            lept_context_pop(c, size * sizeof(lept_value));
            return LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET;
        }
    }
}

static void* lept_context_pop(lept_context* c, size_t size) {
    assert(c->top >= size);
    return c->stack + (c->top -= size);
}

实际上正确的做法是,挨个遍历栈中的lept_value然后释放。我们的栈是按照字节(char)存储的,一个lept_value的大小是24字节,即sizeof(lept_value)=24。所以每次必须要弹出24字节并转为lept_value*。

static int lept_parse_array(lept_context* c, lept_value* v) {
    /*size must be initialized.*/
    size_t size = 0, i;
    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 (;;) {
        lept_value e;
        lept_init(&e);
        if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) {
            /*Break here.*/
            break;
        }
        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);
            memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c, size), size);
            return LEPT_PARSE_OK;
        }
        else {
            /*Break here.*/
            ret = LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET;
            break;
        }
    }
    
    /*Free resource here.*/
    for (i = 0; i < size; i++) {
        lept_free((lept_value*)lept_context_pop(c, sizeof(lept_value)));
    }
    return ret;
} 

在解析遇到错误时,直接保存返回值后break。在最后遍历栈释放资源。

5. 第 4 节那段代码为什么会有 bug?

见Tutorial的解释,bug来自于:如果提前获取了lept_context_push的压栈指针,在lept_parse_value中,可能会遇到realloc函数扩容导致的悬空指针。

标签:get,Tutorial05,value,parse,Json,lept,解析,LEPT,size
From: https://www.cnblogs.com/muuu520/p/17034954.html

相关文章

  • ThreadLocal源码解析及实战应用
    作者:京东物流闫鹏勃1什么是ThreadLocal?ThreadLocal是一个关于创建线程局部变量的类。通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal......
  • 最新2022年Docker面试题高级面试题及附答案解析
    最新2022年Docker面试题高级面试题及附答案解析全部面试题答案,更新日期:01月30日,直接下载吧!下载链接:高清500+份面试题资料及电子书,累计10000+页大厂面试题PDFDocker......
  • 2022年最全Docker面试题附答案解析大汇总
    2022年最全Docker面试题附答案解析大汇总全部面试题答案,更新日期:01月30日,直接下载吧!下载链接:高清500+份面试题资料及电子书,累计10000+页大厂面试题PDFDocker题1:非......
  • 流量路由技术解析
    作者:十眠流量路由,顾名思义就是将具有某些属性特征的流量,路由到指定的目标。流量路由是流量治理中重要的一环,本节内容将会介绍流量路由常见的场景、流量路由技术的原理以及......
  • UIAutomation.0.8.7B3.samples uia powershell 插件例子解析
     uiautomationpowershell插件例子解析  作者给出了示例,不过在中文版Windows上需要略微修改下。因为中文版的进程名名字跟程序名字可能不一样。作者给出里例子是按首......
  • Fastjson反序列化漏洞
    前言Fastjson是阿里开发的一个Java库,用于将Java对象序列化为JSON格式,也可将字符串反序列化为Java对象。Fastjson是非常多见的Java反序列化漏洞,CTF中也出现的......
  • 解析字符串(15分)
    解析字符串(15分)题目内容:  输入一个字符串,要求将其中的字母‘n’理解为回车符号’\n’,模拟文件缓冲区读取的数据,并按替换后的数据流解析出其中包括的字符串。(即通......
  • MySql中json类型数据的查询以及在MyBatis-Plus中的使用
    表结构和初始数据新建表结构CREATETABLE`json_test`(`id`intNOTNULLAUTO_INCREMENT,`roles`jsonDEFAULTNULLCOMMENT'角色',`project`jsonDEFAULTNULL......
  • BeautifulSoup解析数据的属性与方法
     1功能描述2pipinstallbs43pipinstalllxml451.实例化一个BeautifulSoup对象,并且将页面源代码数据加载到该对象中6可以将本地的html文档中的数据加......
  • LLVM IR 代码生成与解析器、抽象语法树
    LLVMIR代码生成与解析器、抽象语法树概述将基于词法分析器,为Kaleidoscope构建一个完整的解析器(Parser)。通过解析器,我们可以定义并构造抽象语法树(AbstractSyntaxTre......