从零开始的 JSON 库教程(一):启程

322 阅读4分钟

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

  每一节都按照三个文件分别介绍代码思路和逻辑,叶大佬的教程每一章都包括主题内容和解答两部分,本专栏在介绍时候合二为一,介绍补充完答案后的内容。

  第一节是解析布尔类型:

leptjson.h

首先是json的数据类型:包括空,布尔,浮点数,字符串,数组,对象
用一个枚举类型lept_type表示
null: 表示为 null
boolean : 表示为 true 或 false
number : 浮点数
string : 表示为 "abc"
array : 表示为 [null,true,number,string,[,,]]
object : 表示为{"k1":null,"k2":true,"k3":123,"k4":"value4","k5":[1,2],"k6":{}}

typedef enum {
    LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LRPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT
} lept_type;

lept_value为json值(可以理解为json对象),由于第一章只检测空和布尔,所以里面只有对象类型(空和布尔类型的值即为他们的类型) 后续会加入字符串的内容,浮点数的数值,数组的成员、大小、容量,对象的成员、大小、容量,

typedef struct {
    lept_type type;
}lept_value;

解析器解析结果,前几章都是一个个检测,即一次检测只能检测一种类型,不能检测复合的json对象(第六章介绍)
一开始只能检测"123"是数字,"true"是布尔,""abc""是字符串

enum {
    LEPT_PARSE_OK = 0,                // 无错误
    LEPT_PARSE_EXPECT_VALUE,          // 空, 如 " ",""
    LEPT_PARSE_INVALID_VALUE,         // 无效值,检测结果不是null,true,false,
    LEPT_PARSE_ROOT_NOT_SINGULAR      // 结果后面还有其他值,"null n"
};
int lept_parse(lept_value* v, const char* json);  // 解析函数,传入字符串,解析出json对象
lept_type lept_get_type(const lept_value* v);     
// 访问解析结果,这个只访问对象类型:如"null"为LEPT_NULL,"123"为LRPT_NUMBER

leptjson.cpp

如果第一个字符是自己想要的,则指针移动一步

#define EXPECT(c, ch) do{assert(*c->json==(ch));c->json++;}while(0)
// json语句,字符串
typedef struct {
    const char* json;
}lept_context;

解析空白(空格 制表 换行 回车),遇到空格、制表、换行、回车,指针后移一步

static void lept_parse_whitespace(lept_context* c) {   
    const char* p = c->json;
    while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') {
            p++;
    }
    c->json = p; 
}

解析LEPT_NULL类型

static int lept_parse_null(lept_context* c, lept_value* v) {  
    EXPECT(c, 'n');
    if (c->json[0] != 'u' || c->json[1] != 'l' || c->json[2] != 'l') {
            return LEPT_PARSE_INVALID_VALUE; // 如果不是null,则是无效值
    }
    c->json += 3;          // 是null;
    v->type = LEPT_NULL;
    return LEPT_PARSE_OK;
}

解析值函数: 遇到 n 尝试解析LEPT_NULL类型
t ➔ true
f ➔ false
" ➔ string
0 - 9 / -➔ number
[➔ array { ➔ object

static int lept_parse_value(lept_context* c, lept_value* v) {
    switch (*c->json) {
        case 'n': return lept_parse_null(c, v);
        case 't': return lept_parse_true(c, v);
        case 'f': return lept_parse_false(c, v);
        case '\0': return LEPT_PARSE_EXPECT_VALUE;   // 文件结尾
        default: return LEPT_PARSE_INVALID_VALUE;    // 目前还没加入字符串,浮点数,数组,对象;如果不是第一个字符上述。则是无效值
    }
}

解析函数
字符串先去除空白,再检查是不是true、false、null,如果是的话,再去除空白,再检查后面有没有字符; 如果有,则解析失败,错误类型为LEPT_PARSE_ROOT_NOT_SINGULAR,如"null n"
目前只能解析单个类型,无法解析复合类型,所以只会lept_parse_value()一次 引入数组和对象后,解析完一个后还要检查后面。如[[1,2,{"key","value"}]]

int lept_parse(lept_value* v, const char* json) {
    lept_context c;      // 理解为json字符串
    assert(v != NULL);  // v不能为空,必须指向一个lept_value(可以理解为json对象)
    c.json = json;
    v->type = LEPT_NULL;
    lept_parse_whitespace(&c);
    int ret = lept_parse_value(&c, v);
    if (ret == LEPT_PARSE_OK) {
        lept_parse_whitespace(&c);
        if (*c.json != '\0') {
            ret = LEPT_PARSE_ROOT_NOT_SINGULAR;
        }
    }
    return ret;
}

test.cpp

这里我们希望我们的解析函数可以解析出所有json字符串,不管它是否合法,合法的给出解析结果为OK,返回json值;非法的解析结果为对应的预期值即可,如解析空白,预期解析结果LEPT_PARSE_EXPECT_VALUE,但json值里类型为null;只要解析的结果与预期相符合,就说明我们函数写对了。

尝试解析"null",解析成功,类型为LEPT_NULL

static void test_parse_null() {
    lept_value v;
    v.type = LEPT_FALSE;
    EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null")); 
    EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); 
}

尝试解析""空白,解析结果是LEPT_PARSE_EXPECT_VALUE(解析结果正确,但这种应该非法(或者说是字符串)),类型为LEPT_NULL

static void test_parse_expect_value() {
    lept_value v;
    v.type = LEPT_FALSE;
    EXPECT_EQ_INT(LEPT_PARSE_EXPECT_VALUE, lept_parse(&v, "")); 
    EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));               
    v.type = LEPT_FALSE;
    EXPECT_EQ_INT(LEPT_PARSE_EXPECT_VALUE, lept_parse(&v, " "));
    EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));
}

尝试解析"nul","?"等无效值空白,解析结果是LEPT_PARSE_INVALID_VALUE, 类型为LEPT_NULL

static void test_parse_invalid_value() {
    lept_value v;
    v.type = LEPT_FALSE;
    EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "nul")); 
    EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));                    
    v.type = LEPT_FALSE;
    EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "?")); 
    EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); // 
}

尝试解析"null x", "true x"等无效值,解析结果应该为LEPT_PARSE_ROOT_NOT_SINGULAR, 但类型应该为LEPT_NULL;
这里他的判断逻辑,解析这种正确值后面还跟有非空字符的,把类型归为正确值的类型,我个人觉得不对,应该均为LEPT_NULL
否则"true x"为LEPT_TRUE,显然不对

static void test_parse_root_not_singular() {
    lept_value v;
    v.type = LEPT_FALSE;
    EXPECT_EQ_INT(LEPT_PARSE_ROOT_NOT_SINGULAR, lept_parse(&v, "null x")); 
    EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));                           
    v.type = LEPT_FALSE;
    EXPECT_EQ_INT(LEPT_PARSE_ROOT_NOT_SINGULAR, lept_parse(&v, "true x")); 
    EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v));                           
}

修正leptjson.cpp中lept_parse逻辑,检测到解析结果为LEPT_PARSE_ROOT_NOT_SINGULAR时,类型设置为LEPT_NULL(非法)

int lept_parse(lept_value* v, const char* json) {
    lept_context c;      // 理解为json字符串
    assert(v != NULL);  // v不能为空,必须指向一个lept_value(可以理解为json对象)
    c.json = json;
    v->type = LEPT_NULL;
    lept_parse_whitespace(&c);
    int ret = lept_parse_value(&c, v);
    if (ret == LEPT_PARSE_OK) {
        lept_parse_whitespace(&c);
        if (*c.json != '\0') {
            ret = LEPT_PARSE_ROOT_NOT_SINGULAR;
            v->type = LEPT_NULL;  // 这里加一行
        }
    }
    return ret;
}

解析结果

教程1.png