• 从零开始的 JSON 库教程(一):启程解答篇
    • 1. 修正 LEPT_PARSE_ROOT_NOT_SINGULAR
    • 2. true/false 单元测试
      • 3. true/false 解析
    • 4. 总结

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

    • Milo Yip
    • 2016/9/17

    本文是《从零开始的 JSON 库教程》的第一个单元解答篇。解答代码位于 json-tutorial/tutorial01_answer。

    1. 修正 LEPT_PARSE_ROOT_NOT_SINGULAR

    单元测试失败的是这一行:

    1. EXPECT_EQ_INT(LEPT_PARSE_ROOT_NOT_SINGULAR, lept_parse(&v, "null x"));

    我们从 JSON 语法发现,JSON 文本应该有 3 部分:

    1. JSON-text = ws value ws

    但原来的 lept_parse() 只处理了前两部分。我们只需要加入第三部分,解析空白,然后检查 JSON 文本是否完结:

    1. int lept_parse(lept_value* v, const char* json) {
    2. lept_context c;
    3. int ret;
    4. assert(v != NULL);
    5. c.json = json;
    6. v->type = LEPT_NULL;
    7. lept_parse_whitespace(&c);
    8. if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) {
    9. lept_parse_whitespace(&c);
    10. if (*c.json != '\0')
    11. ret = LEPT_PARSE_ROOT_NOT_SINGULAR;
    12. }
    13. return ret;
    14. }

    有一些 JSON 解析器完整解析一个值之后就会顺利返回,这是不符合标准的。但有时候也有另一种需求,文本中含多个 JSON 或其他文本串接在一起,希望当完整解析一个值之后就停下来。因此,有一些 JSON 解析器会提供这种选项,例如 RapidJSON 的 kParseStopWhenDoneFlag

    2. true/false 单元测试

    此问题很简单,只需参考 test_parse_null() 加入两个测试函数:

    1. static void test_parse_true() {
    2. lept_value v;
    3. v.type = LEPT_FALSE;
    4. EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true"));
    5. EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v));
    6. }
    7. static void test_parse_false() {
    8. lept_value v;
    9. v.type = LEPT_TRUE;
    10. EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false"));
    11. EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v));
    12. }
    13. static void test_parse() {
    14. test_parse_null();
    15. test_parse_true();
    16. test_parse_false();
    17. test_parse_expect_value();
    18. test_parse_invalid_value();
    19. test_parse_root_not_singular();
    20. }

    但要记得在上一级的测试函数 test_parse() 调用这函数,否则会不起作用。还好如果我们记得用 static 修饰这两个函数,编译器会发出告警:

    1. test.c:30:13: warning: unused function 'test_parse_true' [-Wunused-function]
    2. static void test_parse_true() {
    3. ^

    因为 static 函数的意思是指,该函数只作用于编译单元中,那么没有被调用时,编译器是能发现的。

    3. true/false 解析

    这部分很简单,只要参考 lept_parse_null(),再写两个函数,然后在 lept_parse_value 按首字符分派。

    1. static int lept_parse_true(lept_context* c, lept_value* v) {
    2. EXPECT(c, 't');
    3. if (c->json[0] != 'r' || c->json[1] != 'u' || c->json[2] != 'e')
    4. return LEPT_PARSE_INVALID_VALUE;
    5. c->json += 3;
    6. v->type = LEPT_TRUE;
    7. return LEPT_PARSE_OK;
    8. }
    9. static int lept_parse_false(lept_context* c, lept_value* v) {
    10. EXPECT(c, 'f');
    11. if (c->json[0] != 'a' || c->json[1] != 'l' || c->json[2] != 's' || c->json[3] != 'e')
    12. return LEPT_PARSE_INVALID_VALUE;
    13. c->json += 4;
    14. v->type = LEPT_FALSE;
    15. return LEPT_PARSE_OK;
    16. }
    17. static int lept_parse_value(lept_context* c, lept_value* v) {
    18. switch (*c->json) {
    19. case 't': return lept_parse_true(c, v);
    20. case 'f': return lept_parse_false(c, v);
    21. case 'n': return lept_parse_null(c, v);
    22. case '\0': return LEPT_PARSE_EXPECT_VALUE;
    23. default: return LEPT_PARSE_INVALID_VALUE;
    24. }
    25. }

    其实这 3 种类型都是解析字面量,可以使用单一个函数实现,例如用这种方式调用:

    1. case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL);

    这样可以减少一些重复代码,不过可能有少许额外性能开销。

    4. 总结

    如果你能完成这个练习,恭喜你!我想你通过亲自动手,会对教程里所说的有更深入的理解。如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 issue 中提出,让所有人一起讨论。

    下一单元是和数字类型相关,敬请期待。