|
| 1 | +% 从零开始的 JSON 库教程(一):启程解答编 |
| 2 | +% Milo Yip |
| 3 | +% 2016/9/16 |
| 4 | + |
| 5 | +本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/p/22457315)的第一个单元。解答代码位于 [json-tutorial/tutorial01_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial01_answer/)。 |
| 6 | + |
| 7 | +## 1. 修正 LEPT_PARSE_ROOT_NOT_SINGULAR |
| 8 | + |
| 9 | +单元测试失败的是这一行: |
| 10 | + |
| 11 | +~~~c |
| 12 | +EXPECT_EQ_INT(LEPT_PARSE_ROOT_NOT_SINGULAR, lept_parse(&v, "null x")); |
| 13 | +~~~ |
| 14 | +
|
| 15 | +我们从 JSON 语法发现,JSON 文本应该有 3 部分: |
| 16 | +
|
| 17 | +~~~ |
| 18 | +JSON-text = ws value ws |
| 19 | +~~~ |
| 20 | +
|
| 21 | +但原来的 `lept_parse()` 只处理了前两部分。我们只需要加入第三部分,解析空白,然后检查 JSON 文本是否完结: |
| 22 | +
|
| 23 | +~~~c |
| 24 | +int lept_parse(lept_value* v, const char* json) { |
| 25 | + lept_context c; |
| 26 | + int ret; |
| 27 | + assert(v != NULL); |
| 28 | + c.json = json; |
| 29 | + v->type = LEPT_NULL; |
| 30 | + lept_parse_whitespace(&c); |
| 31 | + if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { |
| 32 | + lept_parse_whitespace(&c); |
| 33 | + if (*c.json != '\0') |
| 34 | + ret = LEPT_PARSE_ROOT_NOT_SINGULAR; |
| 35 | + } |
| 36 | + return ret; |
| 37 | +} |
| 38 | +~~~ |
| 39 | + |
| 40 | +有一些 JSON 解析器完整解析一个值之后就会顺利返回,这是不符合标准的。但有时候也有另一种需求,文本中含多个 JSON 或其他文本串接在一起,希望当完整解析一个值之后就停下来。因此,有一些 JSON 解析器会提供这种选项,例如 RapidJSON 的 `kParseStopWhenDoneFlag`。 |
| 41 | + |
| 42 | +## 2. true/false 单元测试 |
| 43 | + |
| 44 | +此问题很简单,只需参考 `test_parse_null()` 加入两个测试函数: |
| 45 | + |
| 46 | +~~~c |
| 47 | +static void test_parse_true() { |
| 48 | + lept_value v; |
| 49 | + v.type = LEPT_FALSE; |
| 50 | + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); |
| 51 | + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v)); |
| 52 | +} |
| 53 | + |
| 54 | +static void test_parse_false() { |
| 55 | + lept_value v; |
| 56 | + v.type = LEPT_TRUE; |
| 57 | + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false")); |
| 58 | + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); |
| 59 | +} |
| 60 | + |
| 61 | +static void test_parse() { |
| 62 | + test_parse_null(); |
| 63 | + test_parse_true(); |
| 64 | + test_parse_false(); |
| 65 | + test_parse_expect_value(); |
| 66 | + test_parse_invalid_value(); |
| 67 | + test_parse_root_not_singular(); |
| 68 | +} |
| 69 | +~~~ |
| 70 | + |
| 71 | +但要记得在上一级的测试函数 test_parse() 调用这函数,否则会不起作用。还好如果我们记得用 `static` 修饰这两个函数,编译器会发出告警: |
| 72 | + |
| 73 | +~~~ |
| 74 | +test.c:30:13: warning: unused function 'test_parse_true' [-Wunused-function] |
| 75 | +static void test_parse_true() { |
| 76 | + ^ |
| 77 | +~~~ |
| 78 | + |
| 79 | +因为 static 函数的意思是指,该函数只作用于编译单元中,那么没有被调用时,编译器是能发现的。 |
| 80 | + |
| 81 | +### 3. true/false 解析 |
| 82 | + |
| 83 | +这部分很简单,只要参考 `lept_parse_null()`,再写两个函数,然后在 `lept_parse_value` 按首字符分派。 |
| 84 | + |
| 85 | +~~~c |
| 86 | +static int lept_parse_true(lept_context* c, lept_value* v) { |
| 87 | + EXPECT(c, 't'); |
| 88 | + if (c->json[0] != 'r' || c->json[1] != 'u' || c->json[2] != 'e') |
| 89 | + return LEPT_PARSE_INVALID_VALUE; |
| 90 | + c->json += 3; |
| 91 | + v->type = LEPT_TRUE; |
| 92 | + return LEPT_PARSE_OK; |
| 93 | +} |
| 94 | + |
| 95 | +static int lept_parse_false(lept_context* c, lept_value* v) { |
| 96 | + EXPECT(c, 'f'); |
| 97 | + if (c->json[0] != 'a' || c->json[1] != 'l' || c->json[2] != 's' || c->json[3] != 'e') |
| 98 | + return LEPT_PARSE_INVALID_VALUE; |
| 99 | + c->json += 4; |
| 100 | + v->type = LEPT_FALSE; |
| 101 | + return LEPT_PARSE_OK; |
| 102 | +} |
| 103 | + |
| 104 | +static int lept_parse_value(lept_context* c, lept_value* v) { |
| 105 | + switch (*c->json) { |
| 106 | + case 't': return lept_parse_true(c, v); |
| 107 | + case 'f': return lept_parse_false(c, v); |
| 108 | + case 'n': return lept_parse_null(c, v); |
| 109 | + case '\0': return LEPT_PARSE_EXPECT_VALUE; |
| 110 | + default: return LEPT_PARSE_INVALID_VALUE; |
| 111 | + } |
| 112 | +} |
| 113 | +~~~ |
| 114 | +
|
| 115 | +其实这 3 种类型都是解析字面量,可以使用单一个函数实现,例如用这种方式调用: |
| 116 | +
|
| 117 | +~~~c |
| 118 | + case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL); |
| 119 | +~~~ |
| 120 | + |
| 121 | +这样可以减少一些重复代码,不过可能有少许额外性能开销。 |
| 122 | + |
| 123 | +## 4. 总结 |
| 124 | + |
| 125 | +如果你能完成这个练习,恭喜你!我想你通过亲自动手,会对教程里所说的有更深入的理解。如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 |
| 126 | + |
| 127 | +下一单元是和数字类型相关,敬请期待。 |
0 commit comments