├── .gitignore ├── readme.md ├── tutorial01 ├── CMakeLists.txt ├── images │ ├── cmake-gui.png │ ├── makefile │ ├── requirement.dot │ └── requirement.png ├── leptjson.c ├── leptjson.h ├── test.c └── tutorial01.md ├── tutorial01_answer ├── CMakeLists.txt ├── leptjson.c ├── leptjson.h ├── test.c └── tutorial01_answer.md ├── tutorial02 ├── CMakeLists.txt ├── images │ └── number.png ├── leptjson.c ├── leptjson.h ├── test.c └── tutorial02.md ├── tutorial02_answer ├── CMakeLists.txt ├── leptjson.c ├── leptjson.h ├── test.c └── tutorial02_answer.md ├── tutorial03 ├── CMakeLists.txt ├── images │ ├── makefile │ ├── union_layout.dot │ └── union_layout.png ├── leptjson.c ├── leptjson.h ├── test.c └── tutorial03.md ├── tutorial03_answer ├── CMakeLists.txt ├── leptjson.c ├── leptjson.h ├── test.c └── tutorial03_answer.md ├── tutorial04 ├── CMakeLists.txt ├── images │ └── Utf8webgrowth.png ├── leptjson.c ├── leptjson.h ├── test.c └── tutorial04.md ├── tutorial04_answer ├── CMakeLists.txt ├── leptjson.c ├── leptjson.h ├── test.c └── tutorial04_answer.md ├── tutorial05 ├── CMakeLists.txt ├── images │ ├── makefile │ ├── parse_array01.dot │ ├── parse_array01.png │ ├── parse_array02.dot │ ├── parse_array02.png │ ├── parse_array03.dot │ ├── parse_array03.png │ ├── parse_array04.dot │ ├── parse_array04.png │ ├── parse_array05.dot │ ├── parse_array05.png │ ├── parse_array06.dot │ ├── parse_array06.png │ ├── parse_array07.dot │ ├── parse_array07.png │ ├── parse_array08.dot │ ├── parse_array08.png │ ├── parse_array09.dot │ ├── parse_array09.png │ ├── parse_array10.dot │ └── parse_array10.png ├── leptjson.c ├── leptjson.h ├── test.c └── tutorial05.md ├── tutorial05_answer ├── CMakeLists.txt ├── leptjson.c ├── leptjson.h ├── test.c └── tutorial05_answer.md ├── tutorial06 ├── CMakeLists.txt ├── leptjson.c ├── leptjson.h ├── test.c └── tutorial06.md ├── tutorial06_answer ├── CMakeLists.txt ├── leptjson.c ├── leptjson.h ├── test.c └── tutorial06_answer.md ├── tutorial07 ├── CMakeLists.txt ├── images │ ├── makefile │ ├── parse_stringify.dot │ └── parse_stringify.png ├── leptjson.c ├── leptjson.h ├── test.c └── tutorial07.md ├── tutorial07_answer ├── CMakeLists.txt ├── leptjson.c ├── leptjson.h ├── test.c └── tutorial07_answer.md └── tutorial08 ├── CMakeLists.txt ├── leptjson.c ├── leptjson.h ├── test.c └── tutorial08.md /.gitignore: -------------------------------------------------------------------------------- 1 | */build/ 2 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 从零开始的 JSON 库教程 2 | 3 | * Milo Yip 4 | * 2016/9/15 5 | 6 | 也许有很多同学上过 C/C++ 的课后,可以完成一些简单的编程练习,又能在一些网站刷题,但对于如何开发有实际用途的程序可能感到束手无策。本教程希望能以一个简单的项目开发形式,让同学能逐步理解如何从无到有去开发软件。 7 | 8 | 为什么选择 JSON?因为它足够简单,除基本编程外不需大量技术背景知识。JSON 有标准,可按照标准逐步实现。JSON 也是实际在许多应用上会使用的格式,所以才会有大量的开源库。 9 | 10 | 这是一个免费、开源的教程,如果你喜欢,也可以打赏鼓励。因为工作及家庭因素,不能保证每篇文章的首发时间,请各位见谅。 11 | 12 | ## 对象与目标 13 | 14 | 教程对象:学习过基本 C/C++ 编程的同学。 15 | 16 | 通过这个教程,同学可以了解如何从零开始写一个 JSON 库,其特性如下: 17 | 18 | * 符合标准的 JSON 解析器和生成器 19 | * 手写的递归下降解析器(recursive descent parser) 20 | * 使用标准 C 语言(C89) 21 | * 跨平台/编译器(如 Windows/Linux/OS X,vc/gcc/clang) 22 | * 仅支持 UTF-8 JSON 文本 23 | * 仅支持以 `double` 存储 JSON number 类型 24 | * 解析器和生成器的代码合共少于 500 行 25 | 26 | 除了围绕 JSON 作为例子,希望能在教程中讲述一些课题: 27 | 28 | * 测试驱动开发(test driven development, TDD) 29 | * C 语言编程风格 30 | * 数据结构 31 | * API 设计 32 | * 断言 33 | * Unicode 34 | * 浮点数 35 | * Github、CMake、valgrind、Doxygen 等工具 36 | 37 | ## 教程大纲 38 | 39 | 本教程预计分为 9 个单元,第 1-8 个单元附带练习和解答。 40 | 41 | 1. [启程](tutorial01/tutorial01.md)(2016/9/15 完成):编译环境、JSON 简介、测试驱动、解析器主要函数及各数据结构。练习 JSON 布尔类型的解析。[启程解答篇](tutorial01_answer/tutorial01_answer.md)(2016/9/17 完成)。 42 | 2. [解析数字](tutorial02/tutorial02.md)(2016/9/18 完成):JSON number 的语法。练习 JSON number 类型的校验。[解析数字解答篇](tutorial02_answer/tutorial02_answer.md)(2016/9/20 完成)。 43 | 3. [解析字符串](tutorial03/tutorial03.md)(2016/9/22 完成):使用 union 存储 variant、自动扩展的堆栈、JSON string 的语法、valgrind。练习最基本的 JSON string 类型的解析、内存释放。[解析字符串解答篇](tutorial03_answer/tutorial03_answer.md)(2016/9/27 完成)。 44 | 4. [Unicode](tutorial04/tutorial04.md)(2016/10/2 完成):Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。[Unicode 解答篇](tutorial04_answer/tutorial04_answer.md)(2016/10/6 完成)。 45 | 5. [解析数组](tutorial05/tutorial05.md)(2016/10/7 完成):JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。[解析数组解答篇](tutorial05_answer/tutorial05_answer.md)(2016/10/13 完成)。 46 | 6. [解析对象](tutorial06/tutorial06.md)(2016/10/29 完成):JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。[解析对象解答篇](tutorial06_answer/tutorial06_answer.md)(2016/11/15 完成)。 47 | 7. [生成器](tutorial07/tutorial07.md)(2016/12/20 完成):JSON 生成过程、注意事项。练习完成 JSON 生成器。[生成器解答篇](tutorial07_answer/tutorial07_answer.md)(2017/1/5 完成)。 48 | 8. [访问与其他功能](tutorial08/tutorial08.md)(2018/6/2 完成):JSON array/object 的访问及修改。练习完成相关功能。 49 | 9. 终点及新开始:加入 nativejson-benchmark 测试,与 RapidJSON 对比及展望。 50 | 51 | ## 关于作者 52 | 53 | 叶劲峰(Milo Yip)现任腾讯 T4 专家、互动娱乐事业群魔方工作室群游戏客户端技术总监。他获得香港大学认知科学学士(BCogSc)、香港中文大学系统工程及工程管理哲学硕士(MPhil)。他是《游戏引擎架构》译者、《C++ Primer 中文版(第五版)》审校。他曾参与《天涯明月刀》、《斗战神》、《爱丽丝:疯狂回归》、《美食从天降》、《王子传奇》等游戏项目,以及多个游戏引擎及中间件的研发。他是开源项目 [RapidJSON](https://github.com/miloyip/rapidjson) 的作者,开发 [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark) 比较 41 个开源原生 JSON 库的标准符合程度及性能。他在 1990 年学习 C 语言,1995 年开始使用 C++ 于各种项目。 54 | -------------------------------------------------------------------------------- /tutorial01/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.6) 2 | project (leptjson_test C) 3 | 4 | if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") 6 | endif() 7 | 8 | add_library(leptjson leptjson.c) 9 | add_executable(leptjson_test test.c) 10 | target_link_libraries(leptjson_test leptjson) 11 | -------------------------------------------------------------------------------- /tutorial01/images/cmake-gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/json-tutorial/645d80e849137c85e5a9e54982a9fb50a462e5ea/tutorial01/images/cmake-gui.png -------------------------------------------------------------------------------- /tutorial01/images/makefile: -------------------------------------------------------------------------------- 1 | %.png: %.dot 2 | dot $< -Tpng -o $@ 3 | 4 | DOTFILES = $(basename $(wildcard *.dot)) 5 | all: $(addsuffix .png, $(DOTFILES)) 6 | -------------------------------------------------------------------------------- /tutorial01/images/requirement.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=LR 3 | compound=true 4 | fontname="Inconsolata, Consolas" 5 | fontsize=10 6 | margin="0,0" 7 | ranksep=0.5 8 | nodesep=1 9 | penwidth=0.5 10 | 11 | node [shape=box, fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, style=filled, colorscheme=spectral7] 12 | edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] 13 | 14 | JSON [fillcolor=6] 15 | tree [fillcolor=5] 16 | i [style=invis] 17 | 18 | JSON -> tree [label="1. parse"] 19 | tree -> JSON [label="3. stringify"] 20 | tree -> i [label="2. access", dir=back, arrowtail=normal] 21 | } -------------------------------------------------------------------------------- /tutorial01/images/requirement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/json-tutorial/645d80e849137c85e5a9e54982a9fb50a462e5ea/tutorial01/images/requirement.png -------------------------------------------------------------------------------- /tutorial01/leptjson.c: -------------------------------------------------------------------------------- 1 | #include "leptjson.h" 2 | #include /* assert() */ 3 | #include /* NULL */ 4 | 5 | #define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) 6 | 7 | typedef struct { 8 | const char* json; 9 | }lept_context; 10 | 11 | static void lept_parse_whitespace(lept_context* c) { 12 | const char *p = c->json; 13 | while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') 14 | p++; 15 | c->json = p; 16 | } 17 | 18 | static int lept_parse_null(lept_context* c, lept_value* v) { 19 | EXPECT(c, 'n'); 20 | if (c->json[0] != 'u' || c->json[1] != 'l' || c->json[2] != 'l') 21 | return LEPT_PARSE_INVALID_VALUE; 22 | c->json += 3; 23 | v->type = LEPT_NULL; 24 | return LEPT_PARSE_OK; 25 | } 26 | 27 | static int lept_parse_value(lept_context* c, lept_value* v) { 28 | switch (*c->json) { 29 | case 'n': return lept_parse_null(c, v); 30 | case '\0': return LEPT_PARSE_EXPECT_VALUE; 31 | default: return LEPT_PARSE_INVALID_VALUE; 32 | } 33 | } 34 | 35 | int lept_parse(lept_value* v, const char* json) { 36 | lept_context c; 37 | assert(v != NULL); 38 | c.json = json; 39 | v->type = LEPT_NULL; 40 | lept_parse_whitespace(&c); 41 | return lept_parse_value(&c, v); 42 | } 43 | 44 | lept_type lept_get_type(const lept_value* v) { 45 | assert(v != NULL); 46 | return v->type; 47 | } 48 | -------------------------------------------------------------------------------- /tutorial01/leptjson.h: -------------------------------------------------------------------------------- 1 | #ifndef LEPTJSON_H__ 2 | #define LEPTJSON_H__ 3 | 4 | typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; 5 | 6 | typedef struct { 7 | lept_type type; 8 | }lept_value; 9 | 10 | enum { 11 | LEPT_PARSE_OK = 0, 12 | LEPT_PARSE_EXPECT_VALUE, 13 | LEPT_PARSE_INVALID_VALUE, 14 | LEPT_PARSE_ROOT_NOT_SINGULAR 15 | }; 16 | 17 | int lept_parse(lept_value* v, const char* json); 18 | 19 | lept_type lept_get_type(const lept_value* v); 20 | 21 | #endif /* LEPTJSON_H__ */ 22 | -------------------------------------------------------------------------------- /tutorial01/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "leptjson.h" 5 | 6 | static int main_ret = 0; 7 | static int test_count = 0; 8 | static int test_pass = 0; 9 | 10 | #define EXPECT_EQ_BASE(equality, expect, actual, format) \ 11 | do {\ 12 | test_count++;\ 13 | if (equality)\ 14 | test_pass++;\ 15 | else {\ 16 | fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\ 17 | main_ret = 1;\ 18 | }\ 19 | } while(0) 20 | 21 | #define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") 22 | 23 | static void test_parse_null() { 24 | lept_value v; 25 | v.type = LEPT_FALSE; 26 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null")); 27 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); 28 | } 29 | 30 | static void test_parse_expect_value() { 31 | lept_value v; 32 | 33 | v.type = LEPT_FALSE; 34 | EXPECT_EQ_INT(LEPT_PARSE_EXPECT_VALUE, lept_parse(&v, "")); 35 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); 36 | 37 | v.type = LEPT_FALSE; 38 | EXPECT_EQ_INT(LEPT_PARSE_EXPECT_VALUE, lept_parse(&v, " ")); 39 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); 40 | } 41 | 42 | static void test_parse_invalid_value() { 43 | lept_value v; 44 | v.type = LEPT_FALSE; 45 | EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "nul")); 46 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); 47 | 48 | v.type = LEPT_FALSE; 49 | EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "?")); 50 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); 51 | } 52 | 53 | static void test_parse_root_not_singular() { 54 | lept_value v; 55 | v.type = LEPT_FALSE; 56 | EXPECT_EQ_INT(LEPT_PARSE_ROOT_NOT_SINGULAR, lept_parse(&v, "null x")); 57 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); 58 | } 59 | 60 | static void test_parse() { 61 | test_parse_null(); 62 | test_parse_expect_value(); 63 | test_parse_invalid_value(); 64 | test_parse_root_not_singular(); 65 | } 66 | 67 | int main() { 68 | test_parse(); 69 | printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); 70 | return main_ret; 71 | } 72 | -------------------------------------------------------------------------------- /tutorial01_answer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.6) 2 | project (leptjson_test C) 3 | 4 | if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") 6 | endif() 7 | 8 | add_library(leptjson leptjson.c) 9 | add_executable(leptjson_test test.c) 10 | target_link_libraries(leptjson_test leptjson) 11 | -------------------------------------------------------------------------------- /tutorial01_answer/leptjson.c: -------------------------------------------------------------------------------- 1 | #include "leptjson.h" 2 | #include /* assert() */ 3 | #include /* NULL */ 4 | 5 | #define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) 6 | 7 | typedef struct { 8 | const char* json; 9 | }lept_context; 10 | 11 | static void lept_parse_whitespace(lept_context* c) { 12 | const char *p = c->json; 13 | while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') 14 | p++; 15 | c->json = p; 16 | } 17 | 18 | static int lept_parse_true(lept_context* c, lept_value* v) { 19 | EXPECT(c, 't'); 20 | if (c->json[0] != 'r' || c->json[1] != 'u' || c->json[2] != 'e') 21 | return LEPT_PARSE_INVALID_VALUE; 22 | c->json += 3; 23 | v->type = LEPT_TRUE; 24 | return LEPT_PARSE_OK; 25 | } 26 | 27 | static int lept_parse_false(lept_context* c, lept_value* v) { 28 | EXPECT(c, 'f'); 29 | if (c->json[0] != 'a' || c->json[1] != 'l' || c->json[2] != 's' || c->json[3] != 'e') 30 | return LEPT_PARSE_INVALID_VALUE; 31 | c->json += 4; 32 | v->type = LEPT_FALSE; 33 | return LEPT_PARSE_OK; 34 | } 35 | 36 | static int lept_parse_null(lept_context* c, lept_value* v) { 37 | EXPECT(c, 'n'); 38 | if (c->json[0] != 'u' || c->json[1] != 'l' || c->json[2] != 'l') 39 | return LEPT_PARSE_INVALID_VALUE; 40 | c->json += 3; 41 | v->type = LEPT_NULL; 42 | return LEPT_PARSE_OK; 43 | } 44 | 45 | static int lept_parse_value(lept_context* c, lept_value* v) { 46 | switch (*c->json) { 47 | case 't': return lept_parse_true(c, v); 48 | case 'f': return lept_parse_false(c, v); 49 | case 'n': return lept_parse_null(c, v); 50 | case '\0': return LEPT_PARSE_EXPECT_VALUE; 51 | default: return LEPT_PARSE_INVALID_VALUE; 52 | } 53 | } 54 | 55 | int lept_parse(lept_value* v, const char* json) { 56 | lept_context c; 57 | int ret; 58 | assert(v != NULL); 59 | c.json = json; 60 | v->type = LEPT_NULL; 61 | lept_parse_whitespace(&c); 62 | if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { 63 | lept_parse_whitespace(&c); 64 | if (*c.json != '\0') 65 | ret = LEPT_PARSE_ROOT_NOT_SINGULAR; 66 | } 67 | return ret; 68 | } 69 | 70 | lept_type lept_get_type(const lept_value* v) { 71 | assert(v != NULL); 72 | return v->type; 73 | } 74 | -------------------------------------------------------------------------------- /tutorial01_answer/leptjson.h: -------------------------------------------------------------------------------- 1 | #ifndef LEPTJSON_H__ 2 | #define LEPTJSON_H__ 3 | 4 | typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; 5 | 6 | typedef struct { 7 | lept_type type; 8 | }lept_value; 9 | 10 | enum { 11 | LEPT_PARSE_OK = 0, 12 | LEPT_PARSE_EXPECT_VALUE, 13 | LEPT_PARSE_INVALID_VALUE, 14 | LEPT_PARSE_ROOT_NOT_SINGULAR 15 | }; 16 | 17 | int lept_parse(lept_value* v, const char* json); 18 | 19 | lept_type lept_get_type(const lept_value* v); 20 | 21 | #endif /* LEPTJSON_H__ */ 22 | -------------------------------------------------------------------------------- /tutorial01_answer/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "leptjson.h" 5 | 6 | static int main_ret = 0; 7 | static int test_count = 0; 8 | static int test_pass = 0; 9 | 10 | #define EXPECT_EQ_BASE(equality, expect, actual, format) \ 11 | do {\ 12 | test_count++;\ 13 | if (equality)\ 14 | test_pass++;\ 15 | else {\ 16 | fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\ 17 | main_ret = 1;\ 18 | }\ 19 | } while(0) 20 | 21 | #define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") 22 | 23 | static void test_parse_null() { 24 | lept_value v; 25 | v.type = LEPT_FALSE; 26 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null")); 27 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); 28 | } 29 | 30 | static void test_parse_true() { 31 | lept_value v; 32 | v.type = LEPT_FALSE; 33 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); 34 | EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v)); 35 | } 36 | 37 | static void test_parse_false() { 38 | lept_value v; 39 | v.type = LEPT_TRUE; 40 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false")); 41 | EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); 42 | } 43 | 44 | static void test_parse_expect_value() { 45 | lept_value v; 46 | 47 | v.type = LEPT_FALSE; 48 | EXPECT_EQ_INT(LEPT_PARSE_EXPECT_VALUE, lept_parse(&v, "")); 49 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); 50 | 51 | v.type = LEPT_FALSE; 52 | EXPECT_EQ_INT(LEPT_PARSE_EXPECT_VALUE, lept_parse(&v, " ")); 53 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); 54 | } 55 | 56 | static void test_parse_invalid_value() { 57 | lept_value v; 58 | v.type = LEPT_FALSE; 59 | EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "nul")); 60 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); 61 | 62 | v.type = LEPT_FALSE; 63 | EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "?")); 64 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); 65 | } 66 | 67 | static void test_parse_root_not_singular() { 68 | lept_value v; 69 | v.type = LEPT_FALSE; 70 | EXPECT_EQ_INT(LEPT_PARSE_ROOT_NOT_SINGULAR, lept_parse(&v, "null x")); 71 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); 72 | } 73 | 74 | static void test_parse() { 75 | test_parse_null(); 76 | test_parse_true(); 77 | test_parse_false(); 78 | test_parse_expect_value(); 79 | test_parse_invalid_value(); 80 | test_parse_root_not_singular(); 81 | } 82 | 83 | int main() { 84 | test_parse(); 85 | printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); 86 | return main_ret; 87 | } 88 | -------------------------------------------------------------------------------- /tutorial01_answer/tutorial01_answer.md: -------------------------------------------------------------------------------- 1 | # 从零开始的 JSON 库教程(一):启程解答篇 2 | 3 | * Milo Yip 4 | * 2016/9/17 5 | 6 | 本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第一个单元解答篇。解答代码位于 [json-tutorial/tutorial01_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial01_answer/)。 7 | 8 | ## 1. 修正 LEPT_PARSE_ROOT_NOT_SINGULAR 9 | 10 | 单元测试失败的是这一行: 11 | 12 | ~~~c 13 | EXPECT_EQ_INT(LEPT_PARSE_ROOT_NOT_SINGULAR, lept_parse(&v, "null x")); 14 | ~~~ 15 | 16 | 我们从 JSON 语法发现,JSON 文本应该有 3 部分: 17 | 18 | ~~~ 19 | JSON-text = ws value ws 20 | ~~~ 21 | 22 | 但原来的 `lept_parse()` 只处理了前两部分。我们只需要加入第三部分,解析空白,然后检查 JSON 文本是否完结: 23 | 24 | ~~~c 25 | int lept_parse(lept_value* v, const char* json) { 26 | lept_context c; 27 | int ret; 28 | assert(v != NULL); 29 | c.json = json; 30 | v->type = LEPT_NULL; 31 | lept_parse_whitespace(&c); 32 | if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { 33 | lept_parse_whitespace(&c); 34 | if (*c.json != '\0') 35 | ret = LEPT_PARSE_ROOT_NOT_SINGULAR; 36 | } 37 | return ret; 38 | } 39 | ~~~ 40 | 41 | 有一些 JSON 解析器完整解析一个值之后就会顺利返回,这是不符合标准的。但有时候也有另一种需求,文本中含多个 JSON 或其他文本串接在一起,希望当完整解析一个值之后就停下来。因此,有一些 JSON 解析器会提供这种选项,例如 RapidJSON 的 `kParseStopWhenDoneFlag`。 42 | 43 | ## 2. true/false 单元测试 44 | 45 | 此问题很简单,只需参考 `test_parse_null()` 加入两个测试函数: 46 | 47 | ~~~c 48 | static void test_parse_true() { 49 | lept_value v; 50 | v.type = LEPT_FALSE; 51 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); 52 | EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v)); 53 | } 54 | 55 | static void test_parse_false() { 56 | lept_value v; 57 | v.type = LEPT_TRUE; 58 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false")); 59 | EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); 60 | } 61 | 62 | static void test_parse() { 63 | test_parse_null(); 64 | test_parse_true(); 65 | test_parse_false(); 66 | test_parse_expect_value(); 67 | test_parse_invalid_value(); 68 | test_parse_root_not_singular(); 69 | } 70 | ~~~ 71 | 72 | 但要记得在上一级的测试函数 `test_parse()` 调用这函数,否则会不起作用。还好如果我们记得用 `static` 修饰这两个函数,编译器会发出警告: 73 | 74 | ~~~ 75 | test.c:30:13: warning: unused function 'test_parse_true' [-Wunused-function] 76 | static void test_parse_true() { 77 | ^ 78 | ~~~ 79 | 80 | 因为 static 函数的意思是指,该函数只作用于编译单元中,那么没有被调用时,编译器是能发现的。 81 | 82 | ### 3. true/false 解析 83 | 84 | 这部分很简单,只要参考 `lept_parse_null()`,再写两个函数,然后在 `lept_parse_value` 按首字符分派。 85 | 86 | ~~~c 87 | static int lept_parse_true(lept_context* c, lept_value* v) { 88 | EXPECT(c, 't'); 89 | if (c->json[0] != 'r' || c->json[1] != 'u' || c->json[2] != 'e') 90 | return LEPT_PARSE_INVALID_VALUE; 91 | c->json += 3; 92 | v->type = LEPT_TRUE; 93 | return LEPT_PARSE_OK; 94 | } 95 | 96 | static int lept_parse_false(lept_context* c, lept_value* v) { 97 | EXPECT(c, 'f'); 98 | if (c->json[0] != 'a' || c->json[1] != 'l' || c->json[2] != 's' || c->json[3] != 'e') 99 | return LEPT_PARSE_INVALID_VALUE; 100 | c->json += 4; 101 | v->type = LEPT_FALSE; 102 | return LEPT_PARSE_OK; 103 | } 104 | 105 | static int lept_parse_value(lept_context* c, lept_value* v) { 106 | switch (*c->json) { 107 | case 't': return lept_parse_true(c, v); 108 | case 'f': return lept_parse_false(c, v); 109 | case 'n': return lept_parse_null(c, v); 110 | case '\0': return LEPT_PARSE_EXPECT_VALUE; 111 | default: return LEPT_PARSE_INVALID_VALUE; 112 | } 113 | } 114 | ~~~ 115 | 116 | 其实这 3 种类型都是解析字面量,可以使用单一个函数实现,例如用这种方式调用: 117 | 118 | ~~~c 119 | case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL); 120 | ~~~ 121 | 122 | 这样可以减少一些重复代码,不过可能有少许额外性能开销。 123 | 124 | ## 4. 总结 125 | 126 | 如果你能完成这个练习,恭喜你!我想你通过亲自动手,会对教程里所说的有更深入的理解。如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 127 | 128 | 下一单元是和数字类型相关,敬请期待。 129 | -------------------------------------------------------------------------------- /tutorial02/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.6) 2 | project (leptjson_test C) 3 | 4 | if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") 6 | endif() 7 | 8 | add_library(leptjson leptjson.c) 9 | add_executable(leptjson_test test.c) 10 | target_link_libraries(leptjson_test leptjson) 11 | -------------------------------------------------------------------------------- /tutorial02/images/number.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/json-tutorial/645d80e849137c85e5a9e54982a9fb50a462e5ea/tutorial02/images/number.png -------------------------------------------------------------------------------- /tutorial02/leptjson.c: -------------------------------------------------------------------------------- 1 | #include "leptjson.h" 2 | #include /* assert() */ 3 | #include /* NULL, strtod() */ 4 | 5 | #define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) 6 | 7 | typedef struct { 8 | const char* json; 9 | }lept_context; 10 | 11 | static void lept_parse_whitespace(lept_context* c) { 12 | const char *p = c->json; 13 | while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') 14 | p++; 15 | c->json = p; 16 | } 17 | 18 | static int lept_parse_true(lept_context* c, lept_value* v) { 19 | EXPECT(c, 't'); 20 | if (c->json[0] != 'r' || c->json[1] != 'u' || c->json[2] != 'e') 21 | return LEPT_PARSE_INVALID_VALUE; 22 | c->json += 3; 23 | v->type = LEPT_TRUE; 24 | return LEPT_PARSE_OK; 25 | } 26 | 27 | static int lept_parse_false(lept_context* c, lept_value* v) { 28 | EXPECT(c, 'f'); 29 | if (c->json[0] != 'a' || c->json[1] != 'l' || c->json[2] != 's' || c->json[3] != 'e') 30 | return LEPT_PARSE_INVALID_VALUE; 31 | c->json += 4; 32 | v->type = LEPT_FALSE; 33 | return LEPT_PARSE_OK; 34 | } 35 | 36 | static int lept_parse_null(lept_context* c, lept_value* v) { 37 | EXPECT(c, 'n'); 38 | if (c->json[0] != 'u' || c->json[1] != 'l' || c->json[2] != 'l') 39 | return LEPT_PARSE_INVALID_VALUE; 40 | c->json += 3; 41 | v->type = LEPT_NULL; 42 | return LEPT_PARSE_OK; 43 | } 44 | 45 | static int lept_parse_number(lept_context* c, lept_value* v) { 46 | char* end; 47 | /* \TODO validate number */ 48 | v->n = strtod(c->json, &end); 49 | if (c->json == end) 50 | return LEPT_PARSE_INVALID_VALUE; 51 | c->json = end; 52 | v->type = LEPT_NUMBER; 53 | return LEPT_PARSE_OK; 54 | } 55 | 56 | static int lept_parse_value(lept_context* c, lept_value* v) { 57 | switch (*c->json) { 58 | case 't': return lept_parse_true(c, v); 59 | case 'f': return lept_parse_false(c, v); 60 | case 'n': return lept_parse_null(c, v); 61 | default: return lept_parse_number(c, v); 62 | case '\0': return LEPT_PARSE_EXPECT_VALUE; 63 | } 64 | } 65 | 66 | int lept_parse(lept_value* v, const char* json) { 67 | lept_context c; 68 | int ret; 69 | assert(v != NULL); 70 | c.json = json; 71 | v->type = LEPT_NULL; 72 | lept_parse_whitespace(&c); 73 | if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { 74 | lept_parse_whitespace(&c); 75 | if (*c.json != '\0') { 76 | v->type = LEPT_NULL; 77 | ret = LEPT_PARSE_ROOT_NOT_SINGULAR; 78 | } 79 | } 80 | return ret; 81 | } 82 | 83 | lept_type lept_get_type(const lept_value* v) { 84 | assert(v != NULL); 85 | return v->type; 86 | } 87 | 88 | double lept_get_number(const lept_value* v) { 89 | assert(v != NULL && v->type == LEPT_NUMBER); 90 | return v->n; 91 | } 92 | -------------------------------------------------------------------------------- /tutorial02/leptjson.h: -------------------------------------------------------------------------------- 1 | #ifndef LEPTJSON_H__ 2 | #define LEPTJSON_H__ 3 | 4 | typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; 5 | 6 | typedef struct { 7 | double n; 8 | lept_type type; 9 | }lept_value; 10 | 11 | enum { 12 | LEPT_PARSE_OK = 0, 13 | LEPT_PARSE_EXPECT_VALUE, 14 | LEPT_PARSE_INVALID_VALUE, 15 | LEPT_PARSE_ROOT_NOT_SINGULAR, 16 | LEPT_PARSE_NUMBER_TOO_BIG 17 | }; 18 | 19 | int lept_parse(lept_value* v, const char* json); 20 | 21 | lept_type lept_get_type(const lept_value* v); 22 | 23 | double lept_get_number(const lept_value* v); 24 | 25 | #endif /* LEPTJSON_H__ */ 26 | -------------------------------------------------------------------------------- /tutorial02/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "leptjson.h" 5 | 6 | static int main_ret = 0; 7 | static int test_count = 0; 8 | static int test_pass = 0; 9 | 10 | #define EXPECT_EQ_BASE(equality, expect, actual, format) \ 11 | do {\ 12 | test_count++;\ 13 | if (equality)\ 14 | test_pass++;\ 15 | else {\ 16 | fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\ 17 | main_ret = 1;\ 18 | }\ 19 | } while(0) 20 | 21 | #define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") 22 | #define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g") 23 | 24 | static void test_parse_null() { 25 | lept_value v; 26 | v.type = LEPT_FALSE; 27 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null")); 28 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); 29 | } 30 | 31 | static void test_parse_true() { 32 | lept_value v; 33 | v.type = LEPT_FALSE; 34 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); 35 | EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v)); 36 | } 37 | 38 | static void test_parse_false() { 39 | lept_value v; 40 | v.type = LEPT_TRUE; 41 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false")); 42 | EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); 43 | } 44 | 45 | #define TEST_NUMBER(expect, json)\ 46 | do {\ 47 | lept_value v;\ 48 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ 49 | EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(&v));\ 50 | EXPECT_EQ_DOUBLE(expect, lept_get_number(&v));\ 51 | } while(0) 52 | 53 | static void test_parse_number() { 54 | TEST_NUMBER(0.0, "0"); 55 | TEST_NUMBER(0.0, "-0"); 56 | TEST_NUMBER(0.0, "-0.0"); 57 | TEST_NUMBER(1.0, "1"); 58 | TEST_NUMBER(-1.0, "-1"); 59 | TEST_NUMBER(1.5, "1.5"); 60 | TEST_NUMBER(-1.5, "-1.5"); 61 | TEST_NUMBER(3.1416, "3.1416"); 62 | TEST_NUMBER(1E10, "1E10"); 63 | TEST_NUMBER(1e10, "1e10"); 64 | TEST_NUMBER(1E+10, "1E+10"); 65 | TEST_NUMBER(1E-10, "1E-10"); 66 | TEST_NUMBER(-1E10, "-1E10"); 67 | TEST_NUMBER(-1e10, "-1e10"); 68 | TEST_NUMBER(-1E+10, "-1E+10"); 69 | TEST_NUMBER(-1E-10, "-1E-10"); 70 | TEST_NUMBER(1.234E+10, "1.234E+10"); 71 | TEST_NUMBER(1.234E-10, "1.234E-10"); 72 | TEST_NUMBER(0.0, "1e-10000"); /* must underflow */ 73 | } 74 | 75 | #define TEST_ERROR(error, json)\ 76 | do {\ 77 | lept_value v;\ 78 | v.type = LEPT_FALSE;\ 79 | EXPECT_EQ_INT(error, lept_parse(&v, json));\ 80 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\ 81 | } while(0) 82 | 83 | static void test_parse_expect_value() { 84 | TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); 85 | TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); 86 | } 87 | 88 | static void test_parse_invalid_value() { 89 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); 90 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); 91 | 92 | #if 0 93 | /* invalid number */ 94 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); 95 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); 96 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ 97 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ 98 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); 99 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); 100 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); 101 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); 102 | #endif 103 | } 104 | 105 | static void test_parse_root_not_singular() { 106 | TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); 107 | 108 | #if 0 109 | /* invalid number */ 110 | TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' , 'E' , 'e' or nothing */ 111 | TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); 112 | TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); 113 | #endif 114 | } 115 | 116 | static void test_parse_number_too_big() { 117 | #if 0 118 | TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); 119 | TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); 120 | #endif 121 | } 122 | 123 | static void test_parse() { 124 | test_parse_null(); 125 | test_parse_true(); 126 | test_parse_false(); 127 | test_parse_number(); 128 | test_parse_expect_value(); 129 | test_parse_invalid_value(); 130 | test_parse_root_not_singular(); 131 | test_parse_number_too_big(); 132 | } 133 | 134 | int main() { 135 | test_parse(); 136 | printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); 137 | return main_ret; 138 | } 139 | -------------------------------------------------------------------------------- /tutorial02/tutorial02.md: -------------------------------------------------------------------------------- 1 | # 从零开始的 JSON 库教程(二):解析数字 2 | 3 | * Milo Yip 4 | * 2016/9/18 5 | 6 | 本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第二个单元。本单元的源代码位于 [json-tutorial/tutorial02](https://github.com/miloyip/json-tutorial/blob/master/tutorial02/)。 7 | 8 | 本单元内容: 9 | 10 | 1. [初探重构](#1-初探重构) 11 | 2. [JSON 数字语法](#2-json-数字语法) 12 | 3. [数字表示方式](#3-数字表示方式) 13 | 4. [单元测试](#4-单元测试) 14 | 5. [十进制转换至二进制](#5-十进制转换至二进制) 15 | 6. [总结与练习](#6-总结与练习) 16 | 7. [参考](#7-参考) 17 | 8. [常见问题](#8-常见问题) 18 | 19 | ## 1. 初探重构 20 | 21 | 在讨论解析数字之前,我们再补充 TDD 中的一个步骤──重构(refactoring)。根据[1],重构是一个这样的过程: 22 | 23 | > 在不改变代码外在行为的情况下,对代码作出修改,以改进程序的内部结构。 24 | 25 | 在 TDD 的过程中,我们的目标是编写代码去通过测试。但由于这个目标的引导性太强,我们可能会忽略正确性以外的软件品质。在通过测试之后,代码的正确性得以保证,我们就应该审视现时的代码,看看有没有地方可以改进,而同时能维持测试顺利通过。我们可以安心地做各种修改,因为我们有单元测试,可以判断代码在修改后是否影响原来的行为。 26 | 27 | 那么,哪里要作出修改?Beck 和 Fowler([1] 第 3 章)认为程序员要培养一种判断能力,找出程序中的坏味道。例如,在第一单元的练习中,可能大部分人都会复制 `lept_parse_null()` 的代码,作一些修改,成为 `lept_parse_true()` 和 `lept_parse_false()`。如果我们再审视这 3 个函数,它们非常相似。这违反编程中常说的 DRY(don't repeat yourself)原则。本单元的第一个练习题,就是尝试合并这 3 个函数。 28 | 29 | 另外,我们也可能发现,单元测试代码也有很重复的代码,例如 `test_parse_invalid_value()` 中我们每次测试一个不合法的 JSON 值,都有 4 行相似的代码。我们可以把它用宏的方式把它们简化: 30 | 31 | ~~~c 32 | #define TEST_ERROR(error, json)\ 33 | do {\ 34 | lept_value v;\ 35 | v.type = LEPT_FALSE;\ 36 | EXPECT_EQ_INT(error, lept_parse(&v, json));\ 37 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\ 38 | } while(0) 39 | 40 | static void test_parse_expect_value() { 41 | TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); 42 | TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); 43 | } 44 | ~~~ 45 | 46 | 最后,我希望指出,软件的架构难以用单一标准评分,重构时要考虑平衡各种软件品质。例如上述把 3 个函数合并后,优点是减少重复的代码,维护较容易,但缺点可能是带来性能的少量影响。 47 | 48 | ## 2. JSON 数字语法 49 | 50 | 回归正题,本单元的重点在于解析 JSON number 类型。我们先看看它的语法: 51 | 52 | ~~~ 53 | number = [ "-" ] int [ frac ] [ exp ] 54 | int = "0" / digit1-9 *digit 55 | frac = "." 1*digit 56 | exp = ("e" / "E") ["-" / "+"] 1*digit 57 | ~~~ 58 | 59 | number 是以十进制表示,它主要由 4 部分顺序组成:负号、整数、小数、指数。只有整数是必需部分。注意和直觉可能不同的是,正号是不合法的。 60 | 61 | 整数部分如果是 0 开始,只能是单个 0;而由 1-9 开始的话,可以加任意数量的数字(0-9)。也就是说,`0123` 不是一个合法的 JSON 数字。 62 | 63 | 小数部分比较直观,就是小数点后是一或多个数字(0-9)。 64 | 65 | JSON 可使用科学记数法,指数部分由大写 E 或小写 e 开始,然后可有正负号,之后是一或多个数字(0-9)。 66 | 67 | JSON 标准 [ECMA-404](https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf) 采用图的形式表示语法,也可以更直观地看到解析时可能经过的路径: 68 | 69 | ![number](images/number.png) 70 | 71 | 上一单元的 null、false、true 在解析后,我们只需把它们存储为类型。但对于数字,我们要考虑怎么存储解析后的结果。 72 | 73 | ## 3. 数字表示方式 74 | 75 | 从 JSON 数字的语法,我们可能直观地会认为它应该表示为一个浮点数(floating point number),因为它带有小数和指数部分。然而,标准中并没有限制数字的范围或精度。为简单起见,leptjson 选择以双精度浮点数(C 中的 `double` 类型)来存储 JSON 数字。 76 | 77 | 我们为 `lept_value` 添加成员: 78 | 79 | ~~~c 80 | typedef struct { 81 | double n; 82 | lept_type type; 83 | }lept_value; 84 | ~~~ 85 | 86 | 仅当 `type == LEPT_NUMBER` 时,`n` 才表示 JSON 数字的数值。所以获取该值的 API 是这么实现的: 87 | 88 | ~~~c 89 | double lept_get_number(const lept_value* v) { 90 | assert(v != NULL && v->type == LEPT_NUMBER); 91 | return v->n; 92 | } 93 | ~~~ 94 | 95 | 使用者应确保类型正确,才调用此 API。我们继续使用断言来保证。 96 | 97 | ## 4. 单元测试 98 | 99 | 我们定义了 API 之后,按照 TDD,我们可以先写一些单元测试。这次我们使用多行的宏的减少重复代码: 100 | 101 | ~~~c 102 | #define TEST_NUMBER(expect, json)\ 103 | do {\ 104 | lept_value v;\ 105 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ 106 | EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(&v));\ 107 | EXPECT_EQ_DOUBLE(expect, lept_get_number(&v));\ 108 | } while(0) 109 | 110 | static void test_parse_number() { 111 | TEST_NUMBER(0.0, "0"); 112 | TEST_NUMBER(0.0, "-0"); 113 | TEST_NUMBER(0.0, "-0.0"); 114 | TEST_NUMBER(1.0, "1"); 115 | TEST_NUMBER(-1.0, "-1"); 116 | TEST_NUMBER(1.5, "1.5"); 117 | TEST_NUMBER(-1.5, "-1.5"); 118 | TEST_NUMBER(3.1416, "3.1416"); 119 | TEST_NUMBER(1E10, "1E10"); 120 | TEST_NUMBER(1e10, "1e10"); 121 | TEST_NUMBER(1E+10, "1E+10"); 122 | TEST_NUMBER(1E-10, "1E-10"); 123 | TEST_NUMBER(-1E10, "-1E10"); 124 | TEST_NUMBER(-1e10, "-1e10"); 125 | TEST_NUMBER(-1E+10, "-1E+10"); 126 | TEST_NUMBER(-1E-10, "-1E-10"); 127 | TEST_NUMBER(1.234E+10, "1.234E+10"); 128 | TEST_NUMBER(1.234E-10, "1.234E-10"); 129 | TEST_NUMBER(0.0, "1e-10000"); /* must underflow */ 130 | } 131 | ~~~ 132 | 133 | 以上这些都是很基本的测试用例,也可供调试用。大部分情况下,测试案例不能穷举所有可能性。因此,除了加入一些典型的用例,我们也常会使用一些边界值,例如最大值等。练习中会让同学找一些边界值作为用例。 134 | 135 | 除了这些合法的 JSON,我们也要写一些不合语法的用例: 136 | 137 | ~~~c 138 | static void test_parse_invalid_value() { 139 | /* ... */ 140 | /* invalid number */ 141 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); 142 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); 143 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ 144 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ 145 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); 146 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); 147 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); 148 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); 149 | } 150 | ~~~ 151 | 152 | ## 5. 十进制转换至二进制 153 | 154 | 我们需要把十进制的数字转换成二进制的 `double`。这并不是容易的事情 [2]。为了简单起见,leptjson 将使用标准库的 [`strtod()`](https://en.cppreference.com/w/c/string/byte/strtof) 来进行转换。`strtod()` 可转换 JSON 所要求的格式,但问题是,一些 JSON 不容许的格式,`strtod()` 也可转换,所以我们需要自行做格式校验。 155 | 156 | ~~~c 157 | #include /* NULL, strtod() */ 158 | 159 | static int lept_parse_number(lept_context* c, lept_value* v) { 160 | char* end; 161 | /* \TODO validate number */ 162 | v->n = strtod(c->json, &end); 163 | if (c->json == end) 164 | return LEPT_PARSE_INVALID_VALUE; 165 | c->json = end; 166 | v->type = LEPT_NUMBER; 167 | return LEPT_PARSE_OK; 168 | } 169 | ~~~ 170 | 171 | 加入了 number 后,value 的语法变成: 172 | 173 | ~~~ 174 | value = null / false / true / number 175 | ~~~ 176 | 177 | 记得在第一单元中,我们说可以用一个字符就能得知 value 是什么类型,有 11 个字符可判断 number: 178 | 179 | * 0-9/- ➔ number 180 | 181 | 但是,由于我们在 `lept_parse_number()` 内部将会校验输入是否正确的值,我们可以简单地把余下的情况都交给 `lept_parse_number()`: 182 | 183 | ~~~c 184 | static int lept_parse_value(lept_context* c, lept_value* v) { 185 | switch (*c->json) { 186 | case 't': return lept_parse_true(c, v); 187 | case 'f': return lept_parse_false(c, v); 188 | case 'n': return lept_parse_null(c, v); 189 | default: return lept_parse_number(c, v); 190 | case '\0': return LEPT_PARSE_EXPECT_VALUE; 191 | } 192 | } 193 | ~~~ 194 | 195 | ## 6. 总结与练习 196 | 197 | 本单元讲述了 JSON 数字类型的语法,以及 leptjson 所采用的自行校验+`strtod()`转换为 `double` 的方案。实际上一些 JSON 库会采用更复杂的方案,例如支持 64 位带符号/无符号整数,自行实现转换。以我的个人经验,解析/生成数字类型可以说是 RapidJSON 中最难实现的部分,也是 RapidJSON 高效性能的原因,有机会再另外撰文解释。 198 | 199 | 此外我们谈及,重构与单元测试是互相依赖的软件开发技术,适当地运用可提升软件的品质。之后的单元还会有相关的话题。 200 | 201 | 1. 重构合并 `lept_parse_null()`、`lept_parse_false()`、`lept_parse_true()` 为 `lept_parse_literal()`。 202 | 2. 加入 [维基百科双精度浮点数](https://en.wikipedia.org/wiki/Double-precision_floating-point_format#Double-precision_examples) 的一些边界值至单元测试,如 min subnormal positive double、max double 等。 203 | 3. 去掉 `test_parse_invalid_value()` 和 `test_parse_root_not_singular()` 中的 `#if 0 ... #endif`,执行测试,证实测试失败。按 JSON number 的语法在 lept_parse_number() 校验,不符合标准的情况返回 `LEPT_PARSE_INVALID_VALUE` 错误码。 204 | 4. 去掉 `test_parse_number_too_big` 中的 `#if 0 ... #endif`,执行测试,证实测试失败。仔细阅读 [`strtod()`](https://en.cppreference.com/w/c/string/byte/strtof),看看怎样从返回值得知数值是否过大,以返回 `LEPT_PARSE_NUMBER_TOO_BIG` 错误码。(提示:这里需要 `#include` 额外两个标准库头文件。) 205 | 206 | 以上最重要的是第 3 条题目,就是要校验 JSON 的数字语法。建议可使用以下两个宏去简化一下代码: 207 | 208 | ~~~c 209 | #define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') 210 | #define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') 211 | ~~~ 212 | 213 | 另一提示,在校验成功以后,我们不再使用 `end` 指针去检测 `strtod()` 的正确性,第二个参数可传入 `NULL`。 214 | 215 | 如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 216 | 217 | ## 7. 参考 218 | 219 | [1] Fowler, Martin. Refactoring: improving the design of existing code. Pearson Education India, 2009. 中译本:《重构:改善既有代码的设计》,熊节译,人民邮电出版社,2010年。 220 | 221 | [2] Gay, David M. "Correctly rounded binary-decimal and decimal-binary conversions." Numerical Analysis Manuscript 90-10 (1990). 222 | 223 | ## 8. 常见问题 224 | 225 | 1. 为什么要把一些测试代码以 `#if 0 ... #endif` 禁用? 226 | 227 | 因为在做第 1 个练习题时,我希望能 100% 通过测试,方便做重构。另外,使用 `#if 0 ... #endif` 而不使用 `/* ... */`,是因为 C 的注释不支持嵌套(nested),而 `#if ... #endif` 是支持嵌套的。代码中已有注释时,用 `#if 0 ... #endif` 去禁用代码是一个常用技巧,而且可以把 `0` 改为 `1` 去恢复。 228 | 229 | 2. 科学计数法的指数部分没有对前导零作限制吗?`1E012` 也是合法的吗? 230 | 231 | 是的,这是合法的。JSON 源自于 JavaScript([ECMA-262, 3rd edition](https://www.ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262,%203rd%20edition,%20December%201999.pdf)),数字语法取自 JavaScript 的十进位数字的语法(§7.8.3 Numeric Literals)。整数不容许前导零(leading zero),是因为更久的 JavaScript 版本容许以前导零来表示八进位数字,如 `052 == 42`,这种八进位常数表示方式来自于 [C 语言](https://en.cppreference.com/w/c/language/integer_constant)。禁止前导零避免了可能出现的歧义。但是在指数里就不会出现这个问题。多谢 @Smallay 提出及协助解答这个问题。 232 | 233 | 其他常见问答将会从评论中整理。 234 | -------------------------------------------------------------------------------- /tutorial02_answer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.6) 2 | project (leptjson_test C) 3 | 4 | if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") 6 | endif() 7 | 8 | add_library(leptjson leptjson.c) 9 | add_executable(leptjson_test test.c) 10 | target_link_libraries(leptjson_test leptjson) 11 | -------------------------------------------------------------------------------- /tutorial02_answer/leptjson.c: -------------------------------------------------------------------------------- 1 | #include "leptjson.h" 2 | #include /* assert() */ 3 | #include /* errno, ERANGE */ 4 | #include /* HUGE_VAL */ 5 | #include /* NULL, strtod() */ 6 | 7 | #define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) 8 | #define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') 9 | #define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') 10 | 11 | typedef struct { 12 | const char* json; 13 | }lept_context; 14 | 15 | static void lept_parse_whitespace(lept_context* c) { 16 | const char *p = c->json; 17 | while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') 18 | p++; 19 | c->json = p; 20 | } 21 | 22 | static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { 23 | size_t i; 24 | EXPECT(c, literal[0]); 25 | for (i = 0; literal[i + 1]; i++) 26 | if (c->json[i] != literal[i + 1]) 27 | return LEPT_PARSE_INVALID_VALUE; 28 | c->json += i; 29 | v->type = type; 30 | return LEPT_PARSE_OK; 31 | } 32 | 33 | static int lept_parse_number(lept_context* c, lept_value* v) { 34 | const char* p = c->json; 35 | if (*p == '-') p++; 36 | if (*p == '0') p++; 37 | else { 38 | if (!ISDIGIT1TO9(*p)) return LEPT_PARSE_INVALID_VALUE; 39 | for (p++; ISDIGIT(*p); p++); 40 | } 41 | if (*p == '.') { 42 | p++; 43 | if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; 44 | for (p++; ISDIGIT(*p); p++); 45 | } 46 | if (*p == 'e' || *p == 'E') { 47 | p++; 48 | if (*p == '+' || *p == '-') p++; 49 | if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; 50 | for (p++; ISDIGIT(*p); p++); 51 | } 52 | errno = 0; 53 | v->n = strtod(c->json, NULL); 54 | if (errno == ERANGE && (v->n == HUGE_VAL || v->n == -HUGE_VAL)) 55 | return LEPT_PARSE_NUMBER_TOO_BIG; 56 | v->type = LEPT_NUMBER; 57 | c->json = p; 58 | return LEPT_PARSE_OK; 59 | } 60 | 61 | static int lept_parse_value(lept_context* c, lept_value* v) { 62 | switch (*c->json) { 63 | case 't': return lept_parse_literal(c, v, "true", LEPT_TRUE); 64 | case 'f': return lept_parse_literal(c, v, "false", LEPT_FALSE); 65 | case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL); 66 | default: return lept_parse_number(c, v); 67 | case '\0': return LEPT_PARSE_EXPECT_VALUE; 68 | } 69 | } 70 | 71 | int lept_parse(lept_value* v, const char* json) { 72 | lept_context c; 73 | int ret; 74 | assert(v != NULL); 75 | c.json = json; 76 | v->type = LEPT_NULL; 77 | lept_parse_whitespace(&c); 78 | if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { 79 | lept_parse_whitespace(&c); 80 | if (*c.json != '\0') { 81 | v->type = LEPT_NULL; 82 | ret = LEPT_PARSE_ROOT_NOT_SINGULAR; 83 | } 84 | } 85 | return ret; 86 | } 87 | 88 | lept_type lept_get_type(const lept_value* v) { 89 | assert(v != NULL); 90 | return v->type; 91 | } 92 | 93 | double lept_get_number(const lept_value* v) { 94 | assert(v != NULL && v->type == LEPT_NUMBER); 95 | return v->n; 96 | } 97 | -------------------------------------------------------------------------------- /tutorial02_answer/leptjson.h: -------------------------------------------------------------------------------- 1 | #ifndef LEPTJSON_H__ 2 | #define LEPTJSON_H__ 3 | 4 | typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; 5 | 6 | typedef struct { 7 | double n; 8 | lept_type type; 9 | }lept_value; 10 | 11 | enum { 12 | LEPT_PARSE_OK = 0, 13 | LEPT_PARSE_EXPECT_VALUE, 14 | LEPT_PARSE_INVALID_VALUE, 15 | LEPT_PARSE_ROOT_NOT_SINGULAR, 16 | LEPT_PARSE_NUMBER_TOO_BIG 17 | }; 18 | 19 | int lept_parse(lept_value* v, const char* json); 20 | 21 | lept_type lept_get_type(const lept_value* v); 22 | 23 | double lept_get_number(const lept_value* v); 24 | 25 | #endif /* LEPTJSON_H__ */ 26 | -------------------------------------------------------------------------------- /tutorial02_answer/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "leptjson.h" 5 | 6 | static int main_ret = 0; 7 | static int test_count = 0; 8 | static int test_pass = 0; 9 | 10 | #define EXPECT_EQ_BASE(equality, expect, actual, format) \ 11 | do {\ 12 | test_count++;\ 13 | if (equality)\ 14 | test_pass++;\ 15 | else {\ 16 | fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\ 17 | main_ret = 1;\ 18 | }\ 19 | } while(0) 20 | 21 | #define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") 22 | #define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g") 23 | 24 | static void test_parse_null() { 25 | lept_value v; 26 | v.type = LEPT_FALSE; 27 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null")); 28 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); 29 | } 30 | 31 | static void test_parse_true() { 32 | lept_value v; 33 | v.type = LEPT_FALSE; 34 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); 35 | EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v)); 36 | } 37 | 38 | static void test_parse_false() { 39 | lept_value v; 40 | v.type = LEPT_TRUE; 41 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false")); 42 | EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); 43 | } 44 | 45 | #define TEST_NUMBER(expect, json)\ 46 | do {\ 47 | lept_value v;\ 48 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ 49 | EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(&v));\ 50 | EXPECT_EQ_DOUBLE(expect, lept_get_number(&v));\ 51 | } while(0) 52 | 53 | static void test_parse_number() { 54 | TEST_NUMBER(0.0, "0"); 55 | TEST_NUMBER(0.0, "-0"); 56 | TEST_NUMBER(0.0, "-0.0"); 57 | TEST_NUMBER(1.0, "1"); 58 | TEST_NUMBER(-1.0, "-1"); 59 | TEST_NUMBER(1.5, "1.5"); 60 | TEST_NUMBER(-1.5, "-1.5"); 61 | TEST_NUMBER(3.1416, "3.1416"); 62 | TEST_NUMBER(1E10, "1E10"); 63 | TEST_NUMBER(1e10, "1e10"); 64 | TEST_NUMBER(1E+10, "1E+10"); 65 | TEST_NUMBER(1E-10, "1E-10"); 66 | TEST_NUMBER(-1E10, "-1E10"); 67 | TEST_NUMBER(-1e10, "-1e10"); 68 | TEST_NUMBER(-1E+10, "-1E+10"); 69 | TEST_NUMBER(-1E-10, "-1E-10"); 70 | TEST_NUMBER(1.234E+10, "1.234E+10"); 71 | TEST_NUMBER(1.234E-10, "1.234E-10"); 72 | TEST_NUMBER(0.0, "1e-10000"); /* must underflow */ 73 | 74 | TEST_NUMBER(1.0000000000000002, "1.0000000000000002"); /* the smallest number > 1 */ 75 | TEST_NUMBER( 4.9406564584124654e-324, "4.9406564584124654e-324"); /* minimum denormal */ 76 | TEST_NUMBER(-4.9406564584124654e-324, "-4.9406564584124654e-324"); 77 | TEST_NUMBER( 2.2250738585072009e-308, "2.2250738585072009e-308"); /* Max subnormal double */ 78 | TEST_NUMBER(-2.2250738585072009e-308, "-2.2250738585072009e-308"); 79 | TEST_NUMBER( 2.2250738585072014e-308, "2.2250738585072014e-308"); /* Min normal positive double */ 80 | TEST_NUMBER(-2.2250738585072014e-308, "-2.2250738585072014e-308"); 81 | TEST_NUMBER( 1.7976931348623157e+308, "1.7976931348623157e+308"); /* Max double */ 82 | TEST_NUMBER(-1.7976931348623157e+308, "-1.7976931348623157e+308"); 83 | } 84 | 85 | #define TEST_ERROR(error, json)\ 86 | do {\ 87 | lept_value v;\ 88 | v.type = LEPT_FALSE;\ 89 | EXPECT_EQ_INT(error, lept_parse(&v, json));\ 90 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\ 91 | } while(0) 92 | 93 | static void test_parse_expect_value() { 94 | TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); 95 | TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); 96 | } 97 | 98 | static void test_parse_invalid_value() { 99 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); 100 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); 101 | 102 | /* invalid number */ 103 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); 104 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); 105 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ 106 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ 107 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); 108 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); 109 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); 110 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); 111 | } 112 | 113 | static void test_parse_root_not_singular() { 114 | TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); 115 | 116 | /* invalid number */ 117 | TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ 118 | TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); 119 | TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); 120 | } 121 | 122 | static void test_parse_number_too_big() { 123 | TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); 124 | TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); 125 | } 126 | 127 | static void test_parse() { 128 | test_parse_null(); 129 | test_parse_true(); 130 | test_parse_false(); 131 | test_parse_number(); 132 | test_parse_expect_value(); 133 | test_parse_invalid_value(); 134 | test_parse_root_not_singular(); 135 | test_parse_number_too_big(); 136 | } 137 | 138 | int main() { 139 | test_parse(); 140 | printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); 141 | return main_ret; 142 | } 143 | -------------------------------------------------------------------------------- /tutorial02_answer/tutorial02_answer.md: -------------------------------------------------------------------------------- 1 | # 从零开始的 JSON 库教程(二):解析数字解答篇 2 | 3 | * Milo Yip 4 | * 2016/9/20 5 | 6 | 本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第二个单元解答篇。解答代码位于 [json-tutorial/tutorial02_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial02_answer/)。 7 | 8 | ## 1. 重构合并 9 | 10 | 由于 true / false / null 的字符数量不一样,这个答案以 for 循环作比较,直至 `'\0'`。 11 | 12 | ~~~c 13 | static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { 14 | size_t i; 15 | EXPECT(c, literal[0]); 16 | for (i = 0; literal[i + 1]; i++) 17 | if (c->json[i] != literal[i + 1]) 18 | return LEPT_PARSE_INVALID_VALUE; 19 | c->json += i; 20 | v->type = type; 21 | return LEPT_PARSE_OK; 22 | } 23 | 24 | static int lept_parse_value(lept_context* c, lept_value* v) { 25 | switch (*c->json) { 26 | case 't': return lept_parse_literal(c, v, "true", LEPT_TRUE); 27 | case 'f': return lept_parse_literal(c, v, "false", LEPT_FALSE); 28 | case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL); 29 | /* ... */ 30 | } 31 | } 32 | ~~~ 33 | 34 | 注意在 C 语言中,数组长度、索引值最好使用 `size_t` 类型,而不是 `int` 或 `unsigned`。 35 | 36 | 你也可以直接传送长度参数 4、5、4,只要能通过测试就行了。 37 | 38 | ## 2. 边界值测试 39 | 40 | 这问题其实涉及一些浮点数类型的细节,例如 IEEE-754 浮点数中,有所谓的 normal 和 subnormal 值,这里暂时不展开讨论了。以下是我加入的一些边界值,可能和同学的不完全一样。 41 | 42 | ~~~ 43 | TEST_NUMBER(1.0000000000000002, "1.0000000000000002"); /* the smallest number > 1 */ 44 | TEST_NUMBER( 4.9406564584124654e-324, "4.9406564584124654e-324"); /* minimum denormal */ 45 | TEST_NUMBER(-4.9406564584124654e-324, "-4.9406564584124654e-324"); 46 | TEST_NUMBER( 2.2250738585072009e-308, "2.2250738585072009e-308"); /* Max subnormal double */ 47 | TEST_NUMBER(-2.2250738585072009e-308, "-2.2250738585072009e-308"); 48 | TEST_NUMBER( 2.2250738585072014e-308, "2.2250738585072014e-308"); /* Min normal positive double */ 49 | TEST_NUMBER(-2.2250738585072014e-308, "-2.2250738585072014e-308"); 50 | TEST_NUMBER( 1.7976931348623157e+308, "1.7976931348623157e+308"); /* Max double */ 51 | TEST_NUMBER(-1.7976931348623157e+308, "-1.7976931348623157e+308"); 52 | ~~~ 53 | 54 | 另外,这些加入的测试用例,正常的 `strtod()` 都能通过。所以不能做到测试失败、修改实现、测试成功的 TDD 步骤。 55 | 56 | 有一些 JSON 解析器不使用 `strtod()` 而自行转换,例如在校验的同时,记录负号、尾数(整数和小数)和指数,然后 naive 地计算: 57 | 58 | ~~~c 59 | int negative = 0; 60 | int64_t mantissa = 0; 61 | int exp = 0; 62 | 63 | /* 解析... 并存储 negative, mantissa, exp */ 64 | v->n = (negative ? -mantissa : mantissa) * pow(10.0, exp); 65 | ~~~ 66 | 67 | 这种做法会有精度问题。实现正确的答案是很复杂的,RapidJSON 的初期版本也是 naive 的,后来 RapidJSON 就内部实现了三种算法(使用 `kParseFullPrecision` 选项开启),最后一种算法用到了大整数(高精度计算)。有兴趣的同学也可以先尝试做一个 naive 版本,不使用 `strtod()`。之后可再参考 Google 的 [double-conversion](https://github.com/google/double-conversion) 开源项目及相关论文。 68 | 69 | ## 3. 校验数字 70 | 71 | 这条题目是本单元的重点,考验同学是否能把语法手写为校验规则。我详细说明。 72 | 73 | 首先,如同 `lept_parse_whitespace()`,我们使用一个指针 `p` 来表示当前的解析字符位置。这样做有两个好处,一是代码更简单,二是在某些编译器下性能更好(因为不能确定 `c` 会否被改变,从而每次更改 `c->json` 都要做一次间接访问)。如果校验成功,才把 `p` 赋值至 `c->json`。 74 | 75 | ~~~c 76 | static int lept_parse_number(lept_context* c, lept_value* v) { 77 | const char* p = c->json; 78 | /* 负号 ... */ 79 | /* 整数 ... */ 80 | /* 小数 ... */ 81 | /* 指数 ... */ 82 | v->n = strtod(c->json, NULL); 83 | v->type = LEPT_NUMBER; 84 | c->json = p; 85 | return LEPT_PARSE_OK; 86 | } 87 | ~~~ 88 | 89 | 我们把语法再看一遍: 90 | 91 | ~~~ 92 | number = [ "-" ] int [ frac ] [ exp ] 93 | int = "0" / digit1-9 *digit 94 | frac = "." 1*digit 95 | exp = ("e" / "E") ["-" / "+"] 1*digit 96 | ~~~ 97 | 98 | 负号最简单,有的话跳过便行: 99 | 100 | ~~~c 101 | if (*p == '-') p++; 102 | ~~~ 103 | 104 | 整数部分有两种合法情况,一是单个 `0`,否则是一个 1-9 再加上任意数量的 digit。对于第一种情况,我们像负号般跳过便行。对于第二种情况,第一个字符必须为 1-9,如果否定的就是不合法的,可立即返回错误码。然后,有多少个 digit 就跳过多少个。 105 | 106 | ~~~c 107 | if (*p == '0') p++; 108 | else { 109 | if (!ISDIGIT1TO9(*p)) return LEPT_PARSE_INVALID_VALUE; 110 | for (p++; ISDIGIT(*p); p++); 111 | } 112 | ~~~ 113 | 114 | 如果出现小数点,我们跳过该小数点,然后检查它至少应有一个 digit,不是 digit 就返回错误码。跳过首个 digit,我们再检查有没有 digit,有多少个跳过多少个。这里用了 for 循环技巧来做这件事。 115 | 116 | ~~~c 117 | if (*p == '.') { 118 | p++; 119 | if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; 120 | for (p++; ISDIGIT(*p); p++); 121 | } 122 | ~~~ 123 | 124 | 最后,如果出现大小写 `e`,就表示有指数部分。跳过那个 `e` 之后,可以有一个正或负号,有的话就跳过。然后和小数的逻辑是一样的。 125 | 126 | ~~~c 127 | if (*p == 'e' || *p == 'E') { 128 | p++; 129 | if (*p == '+' || *p == '-') p++; 130 | if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; 131 | for (p++; ISDIGIT(*p); p++); 132 | } 133 | ~~~ 134 | 135 | 这里用了 18 行代码去做这个校验。当中把一些 if 用一行来排版,而没用采用传统两行缩进风格,我个人认为在不影响阅读时可以这样弹性处理。当然那些 for 也可分拆成三行: 136 | 137 | ~~~c 138 | p++; 139 | while (ISDIGIT(*p)) 140 | p++; 141 | ~~~ 142 | 143 | ## 4. 数字过大的处理 144 | 145 | 最后这题纯粹是阅读理解题。 146 | 147 | ~~~c 148 | #include /* errno, ERANGE */ 149 | #include /* HUGE_VAL */ 150 | 151 | static int lept_parse_number(lept_context* c, lept_value* v) { 152 | /* ... */ 153 | errno = 0; 154 | v->n = strtod(c->json, NULL); 155 | if (errno == ERANGE && v->n == HUGE_VAL) return LEPT_PARSE_NUMBER_TOO_BIG; 156 | /* ... */ 157 | } 158 | ~~~ 159 | 160 | 许多时候课本/书籍也不会把每个标准库功能说得很仔细,我想藉此提醒同学要好好看参考文档,学会读文档编程就简单得多![cppreference.com](https://cppreference.com) 是 C/C++ 程序员的宝库。 161 | 162 | ## 5. 总结 163 | 164 | 本单元的习题比上个单元较有挑战性一些,所以我花了多一些篇幅在解答篇。纯以语法来说,数字类型已经是 JSON 中最复杂的类型。如果同学能完成本单元的练习(特别是第 3 条),之后的字符串、数组和对象的语法一定难不到你。然而,接下来也会有一些新挑战,例如内存管理、数据结构、编码等,希望你能满载而归。 165 | 166 | 如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 167 | -------------------------------------------------------------------------------- /tutorial03/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.6) 2 | project (leptjson_test C) 3 | 4 | if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") 6 | endif() 7 | 8 | add_library(leptjson leptjson.c) 9 | add_executable(leptjson_test test.c) 10 | target_link_libraries(leptjson_test leptjson) 11 | -------------------------------------------------------------------------------- /tutorial03/images/makefile: -------------------------------------------------------------------------------- 1 | %.png: %.dot 2 | dot $< -Tpng -o $@ 3 | 4 | DOTFILES = $(basename $(wildcard *.dot)) 5 | all: $(addsuffix .png, $(DOTFILES)) 6 | -------------------------------------------------------------------------------- /tutorial03/images/union_layout.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=LR 3 | compound=true 4 | fontname="Inconsolata, Consolas" 5 | fontsize=10 6 | margin="0,0" 7 | ranksep=0.5 8 | nodesep=1 9 | penwidth=0.5 10 | 11 | node [shape=Mrecord, fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, style=filled, colorscheme=spectral7] 12 | edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] 13 | 14 | old [fillcolor=6, label="double n (8 bytes) |const char* s (4 bytes) |size_t len (4 bytes)|int type (4 bytes)"] 15 | new [fillcolor=5, label="{double n (8 bytes)|{const char* s (4 bytes)|size_t len (4 bytes)}}|int type (4 bytes)"] 16 | old -> new [style=invis] 17 | } -------------------------------------------------------------------------------- /tutorial03/images/union_layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/json-tutorial/645d80e849137c85e5a9e54982a9fb50a462e5ea/tutorial03/images/union_layout.png -------------------------------------------------------------------------------- /tutorial03/leptjson.c: -------------------------------------------------------------------------------- 1 | #include "leptjson.h" 2 | #include /* assert() */ 3 | #include /* errno, ERANGE */ 4 | #include /* HUGE_VAL */ 5 | #include /* NULL, malloc(), realloc(), free(), strtod() */ 6 | #include /* memcpy() */ 7 | 8 | #ifndef LEPT_PARSE_STACK_INIT_SIZE 9 | #define LEPT_PARSE_STACK_INIT_SIZE 256 10 | #endif 11 | 12 | #define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) 13 | #define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') 14 | #define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') 15 | #define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0) 16 | 17 | typedef struct { 18 | const char* json; 19 | char* stack; 20 | size_t size, top; 21 | }lept_context; 22 | 23 | static void* lept_context_push(lept_context* c, size_t size) { 24 | void* ret; 25 | assert(size > 0); 26 | if (c->top + size >= c->size) { 27 | if (c->size == 0) 28 | c->size = LEPT_PARSE_STACK_INIT_SIZE; 29 | while (c->top + size >= c->size) 30 | c->size += c->size >> 1; /* c->size * 1.5 */ 31 | c->stack = (char*)realloc(c->stack, c->size); 32 | } 33 | ret = c->stack + c->top; 34 | c->top += size; 35 | return ret; 36 | } 37 | 38 | static void* lept_context_pop(lept_context* c, size_t size) { 39 | assert(c->top >= size); 40 | return c->stack + (c->top -= size); 41 | } 42 | 43 | static void lept_parse_whitespace(lept_context* c) { 44 | const char *p = c->json; 45 | while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') 46 | p++; 47 | c->json = p; 48 | } 49 | 50 | static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { 51 | size_t i; 52 | EXPECT(c, literal[0]); 53 | for (i = 0; literal[i + 1]; i++) 54 | if (c->json[i] != literal[i + 1]) 55 | return LEPT_PARSE_INVALID_VALUE; 56 | c->json += i; 57 | v->type = type; 58 | return LEPT_PARSE_OK; 59 | } 60 | 61 | static int lept_parse_number(lept_context* c, lept_value* v) { 62 | const char* p = c->json; 63 | if (*p == '-') p++; 64 | if (*p == '0') p++; 65 | else { 66 | if (!ISDIGIT1TO9(*p)) return LEPT_PARSE_INVALID_VALUE; 67 | for (p++; ISDIGIT(*p); p++); 68 | } 69 | if (*p == '.') { 70 | p++; 71 | if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; 72 | for (p++; ISDIGIT(*p); p++); 73 | } 74 | if (*p == 'e' || *p == 'E') { 75 | p++; 76 | if (*p == '+' || *p == '-') p++; 77 | if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; 78 | for (p++; ISDIGIT(*p); p++); 79 | } 80 | errno = 0; 81 | v->u.n = strtod(c->json, NULL); 82 | if (errno == ERANGE && (v->u.n == HUGE_VAL || v->u.n == -HUGE_VAL)) 83 | return LEPT_PARSE_NUMBER_TOO_BIG; 84 | v->type = LEPT_NUMBER; 85 | c->json = p; 86 | return LEPT_PARSE_OK; 87 | } 88 | 89 | static int lept_parse_string(lept_context* c, lept_value* v) { 90 | size_t head = c->top, len; 91 | const char* p; 92 | EXPECT(c, '\"'); 93 | p = c->json; 94 | for (;;) { 95 | char ch = *p++; 96 | switch (ch) { 97 | case '\"': 98 | len = c->top - head; 99 | lept_set_string(v, (const char*)lept_context_pop(c, len), len); 100 | c->json = p; 101 | return LEPT_PARSE_OK; 102 | case '\0': 103 | c->top = head; 104 | return LEPT_PARSE_MISS_QUOTATION_MARK; 105 | default: 106 | PUTC(c, ch); 107 | } 108 | } 109 | } 110 | 111 | static int lept_parse_value(lept_context* c, lept_value* v) { 112 | switch (*c->json) { 113 | case 't': return lept_parse_literal(c, v, "true", LEPT_TRUE); 114 | case 'f': return lept_parse_literal(c, v, "false", LEPT_FALSE); 115 | case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL); 116 | default: return lept_parse_number(c, v); 117 | case '"': return lept_parse_string(c, v); 118 | case '\0': return LEPT_PARSE_EXPECT_VALUE; 119 | } 120 | } 121 | 122 | int lept_parse(lept_value* v, const char* json) { 123 | lept_context c; 124 | int ret; 125 | assert(v != NULL); 126 | c.json = json; 127 | c.stack = NULL; 128 | c.size = c.top = 0; 129 | lept_init(v); 130 | lept_parse_whitespace(&c); 131 | if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { 132 | lept_parse_whitespace(&c); 133 | if (*c.json != '\0') { 134 | v->type = LEPT_NULL; 135 | ret = LEPT_PARSE_ROOT_NOT_SINGULAR; 136 | } 137 | } 138 | assert(c.top == 0); 139 | free(c.stack); 140 | return ret; 141 | } 142 | 143 | void lept_free(lept_value* v) { 144 | assert(v != NULL); 145 | if (v->type == LEPT_STRING) 146 | free(v->u.s.s); 147 | v->type = LEPT_NULL; 148 | } 149 | 150 | lept_type lept_get_type(const lept_value* v) { 151 | assert(v != NULL); 152 | return v->type; 153 | } 154 | 155 | int lept_get_boolean(const lept_value* v) { 156 | /* \TODO */ 157 | return 0; 158 | } 159 | 160 | void lept_set_boolean(lept_value* v, int b) { 161 | /* \TODO */ 162 | } 163 | 164 | double lept_get_number(const lept_value* v) { 165 | assert(v != NULL && v->type == LEPT_NUMBER); 166 | return v->u.n; 167 | } 168 | 169 | void lept_set_number(lept_value* v, double n) { 170 | /* \TODO */ 171 | } 172 | 173 | const char* lept_get_string(const lept_value* v) { 174 | assert(v != NULL && v->type == LEPT_STRING); 175 | return v->u.s.s; 176 | } 177 | 178 | size_t lept_get_string_length(const lept_value* v) { 179 | assert(v != NULL && v->type == LEPT_STRING); 180 | return v->u.s.len; 181 | } 182 | 183 | void lept_set_string(lept_value* v, const char* s, size_t len) { 184 | assert(v != NULL && (s != NULL || len == 0)); 185 | lept_free(v); 186 | v->u.s.s = (char*)malloc(len + 1); 187 | memcpy(v->u.s.s, s, len); 188 | v->u.s.s[len] = '\0'; 189 | v->u.s.len = len; 190 | v->type = LEPT_STRING; 191 | } 192 | -------------------------------------------------------------------------------- /tutorial03/leptjson.h: -------------------------------------------------------------------------------- 1 | #ifndef LEPTJSON_H__ 2 | #define LEPTJSON_H__ 3 | 4 | #include /* size_t */ 5 | 6 | typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; 7 | 8 | typedef struct { 9 | union { 10 | struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ 11 | double n; /* number */ 12 | }u; 13 | lept_type type; 14 | }lept_value; 15 | 16 | enum { 17 | LEPT_PARSE_OK = 0, 18 | LEPT_PARSE_EXPECT_VALUE, 19 | LEPT_PARSE_INVALID_VALUE, 20 | LEPT_PARSE_ROOT_NOT_SINGULAR, 21 | LEPT_PARSE_NUMBER_TOO_BIG, 22 | LEPT_PARSE_MISS_QUOTATION_MARK, 23 | LEPT_PARSE_INVALID_STRING_ESCAPE, 24 | LEPT_PARSE_INVALID_STRING_CHAR 25 | }; 26 | 27 | #define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) 28 | 29 | int lept_parse(lept_value* v, const char* json); 30 | 31 | void lept_free(lept_value* v); 32 | 33 | lept_type lept_get_type(const lept_value* v); 34 | 35 | #define lept_set_null(v) lept_free(v) 36 | 37 | int lept_get_boolean(const lept_value* v); 38 | void lept_set_boolean(lept_value* v, int b); 39 | 40 | double lept_get_number(const lept_value* v); 41 | void lept_set_number(lept_value* v, double n); 42 | 43 | const char* lept_get_string(const lept_value* v); 44 | size_t lept_get_string_length(const lept_value* v); 45 | void lept_set_string(lept_value* v, const char* s, size_t len); 46 | 47 | #endif /* LEPTJSON_H__ */ 48 | -------------------------------------------------------------------------------- /tutorial03/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "leptjson.h" 5 | 6 | static int main_ret = 0; 7 | static int test_count = 0; 8 | static int test_pass = 0; 9 | 10 | #define EXPECT_EQ_BASE(equality, expect, actual, format) \ 11 | do {\ 12 | test_count++;\ 13 | if (equality)\ 14 | test_pass++;\ 15 | else {\ 16 | fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\ 17 | main_ret = 1;\ 18 | }\ 19 | } while(0) 20 | 21 | #define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") 22 | #define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g") 23 | #define EXPECT_EQ_STRING(expect, actual, alength) \ 24 | EXPECT_EQ_BASE(sizeof(expect) - 1 == (alength) && memcmp(expect, actual, alength) == 0, expect, actual, "%s") 25 | #define EXPECT_TRUE(actual) EXPECT_EQ_BASE((actual) != 0, "true", "false", "%s") 26 | #define EXPECT_FALSE(actual) EXPECT_EQ_BASE((actual) == 0, "false", "true", "%s") 27 | 28 | static void test_parse_null() { 29 | lept_value v; 30 | lept_init(&v); 31 | lept_set_boolean(&v, 0); 32 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null")); 33 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); 34 | lept_free(&v); 35 | } 36 | 37 | static void test_parse_true() { 38 | lept_value v; 39 | lept_init(&v); 40 | lept_set_boolean(&v, 0); 41 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); 42 | EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v)); 43 | lept_free(&v); 44 | } 45 | 46 | static void test_parse_false() { 47 | lept_value v; 48 | lept_init(&v); 49 | lept_set_boolean(&v, 1); 50 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false")); 51 | EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); 52 | lept_free(&v); 53 | } 54 | 55 | #define TEST_NUMBER(expect, json)\ 56 | do {\ 57 | lept_value v;\ 58 | lept_init(&v);\ 59 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ 60 | EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(&v));\ 61 | EXPECT_EQ_DOUBLE(expect, lept_get_number(&v));\ 62 | lept_free(&v);\ 63 | } while(0) 64 | 65 | static void test_parse_number() { 66 | TEST_NUMBER(0.0, "0"); 67 | TEST_NUMBER(0.0, "-0"); 68 | TEST_NUMBER(0.0, "-0.0"); 69 | TEST_NUMBER(1.0, "1"); 70 | TEST_NUMBER(-1.0, "-1"); 71 | TEST_NUMBER(1.5, "1.5"); 72 | TEST_NUMBER(-1.5, "-1.5"); 73 | TEST_NUMBER(3.1416, "3.1416"); 74 | TEST_NUMBER(1E10, "1E10"); 75 | TEST_NUMBER(1e10, "1e10"); 76 | TEST_NUMBER(1E+10, "1E+10"); 77 | TEST_NUMBER(1E-10, "1E-10"); 78 | TEST_NUMBER(-1E10, "-1E10"); 79 | TEST_NUMBER(-1e10, "-1e10"); 80 | TEST_NUMBER(-1E+10, "-1E+10"); 81 | TEST_NUMBER(-1E-10, "-1E-10"); 82 | TEST_NUMBER(1.234E+10, "1.234E+10"); 83 | TEST_NUMBER(1.234E-10, "1.234E-10"); 84 | TEST_NUMBER(0.0, "1e-10000"); /* must underflow */ 85 | 86 | TEST_NUMBER(1.0000000000000002, "1.0000000000000002"); /* the smallest number > 1 */ 87 | TEST_NUMBER( 4.9406564584124654e-324, "4.9406564584124654e-324"); /* minimum denormal */ 88 | TEST_NUMBER(-4.9406564584124654e-324, "-4.9406564584124654e-324"); 89 | TEST_NUMBER( 2.2250738585072009e-308, "2.2250738585072009e-308"); /* Max subnormal double */ 90 | TEST_NUMBER(-2.2250738585072009e-308, "-2.2250738585072009e-308"); 91 | TEST_NUMBER( 2.2250738585072014e-308, "2.2250738585072014e-308"); /* Min normal positive double */ 92 | TEST_NUMBER(-2.2250738585072014e-308, "-2.2250738585072014e-308"); 93 | TEST_NUMBER( 1.7976931348623157e+308, "1.7976931348623157e+308"); /* Max double */ 94 | TEST_NUMBER(-1.7976931348623157e+308, "-1.7976931348623157e+308"); 95 | } 96 | 97 | #define TEST_STRING(expect, json)\ 98 | do {\ 99 | lept_value v;\ 100 | lept_init(&v);\ 101 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ 102 | EXPECT_EQ_INT(LEPT_STRING, lept_get_type(&v));\ 103 | EXPECT_EQ_STRING(expect, lept_get_string(&v), lept_get_string_length(&v));\ 104 | lept_free(&v);\ 105 | } while(0) 106 | 107 | static void test_parse_string() { 108 | TEST_STRING("", "\"\""); 109 | TEST_STRING("Hello", "\"Hello\""); 110 | #if 0 111 | TEST_STRING("Hello\nWorld", "\"Hello\\nWorld\""); 112 | TEST_STRING("\" \\ / \b \f \n \r \t", "\"\\\" \\\\ \\/ \\b \\f \\n \\r \\t\""); 113 | #endif 114 | } 115 | 116 | #define TEST_ERROR(error, json)\ 117 | do {\ 118 | lept_value v;\ 119 | lept_init(&v);\ 120 | v.type = LEPT_FALSE;\ 121 | EXPECT_EQ_INT(error, lept_parse(&v, json));\ 122 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\ 123 | lept_free(&v);\ 124 | } while(0) 125 | 126 | static void test_parse_expect_value() { 127 | TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); 128 | TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); 129 | } 130 | 131 | static void test_parse_invalid_value() { 132 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); 133 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); 134 | 135 | /* invalid number */ 136 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); 137 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); 138 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ 139 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ 140 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); 141 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); 142 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); 143 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); 144 | } 145 | 146 | static void test_parse_root_not_singular() { 147 | TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); 148 | 149 | /* invalid number */ 150 | TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ 151 | TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); 152 | TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); 153 | } 154 | 155 | static void test_parse_number_too_big() { 156 | TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); 157 | TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); 158 | } 159 | 160 | static void test_parse_missing_quotation_mark() { 161 | TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\""); 162 | TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\"abc"); 163 | } 164 | 165 | static void test_parse_invalid_string_escape() { 166 | #if 0 167 | TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\v\""); 168 | TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\'\""); 169 | TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\0\""); 170 | TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\x12\""); 171 | #endif 172 | } 173 | 174 | static void test_parse_invalid_string_char() { 175 | #if 0 176 | TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x01\""); 177 | TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x1F\""); 178 | #endif 179 | } 180 | 181 | static void test_access_null() { 182 | lept_value v; 183 | lept_init(&v); 184 | lept_set_string(&v, "a", 1); 185 | lept_set_null(&v); 186 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); 187 | lept_free(&v); 188 | } 189 | 190 | static void test_access_boolean() { 191 | /* \TODO */ 192 | /* Use EXPECT_TRUE() and EXPECT_FALSE() */ 193 | } 194 | 195 | static void test_access_number() { 196 | /* \TODO */ 197 | } 198 | 199 | static void test_access_string() { 200 | lept_value v; 201 | lept_init(&v); 202 | lept_set_string(&v, "", 0); 203 | EXPECT_EQ_STRING("", lept_get_string(&v), lept_get_string_length(&v)); 204 | lept_set_string(&v, "Hello", 5); 205 | EXPECT_EQ_STRING("Hello", lept_get_string(&v), lept_get_string_length(&v)); 206 | lept_free(&v); 207 | } 208 | 209 | static void test_parse() { 210 | test_parse_null(); 211 | test_parse_true(); 212 | test_parse_false(); 213 | test_parse_number(); 214 | test_parse_string(); 215 | test_parse_expect_value(); 216 | test_parse_invalid_value(); 217 | test_parse_root_not_singular(); 218 | test_parse_number_too_big(); 219 | test_parse_missing_quotation_mark(); 220 | test_parse_invalid_string_escape(); 221 | test_parse_invalid_string_char(); 222 | 223 | test_access_null(); 224 | test_access_boolean(); 225 | test_access_number(); 226 | test_access_string(); 227 | } 228 | 229 | int main() { 230 | test_parse(); 231 | printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); 232 | return main_ret; 233 | } 234 | -------------------------------------------------------------------------------- /tutorial03/tutorial03.md: -------------------------------------------------------------------------------- 1 | # 从零开始的 JSON 库教程(三):解析字符串 2 | 3 | * Milo Yip 4 | * 2016/9/22 5 | 6 | 本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第三个单元。本单元的练习源代码位于 [json-tutorial/tutorial03](https://github.com/miloyip/json-tutorial/blob/master/tutorial03)。 7 | 8 | 本单元内容: 9 | 10 | 1. [JSON 字符串语法](#1-json-字符串语法) 11 | 2. [字符串表示](#2-字符串表示) 12 | 3. [内存管理](#3-内存管理) 13 | 4. [缓冲区与堆栈](#4-缓冲区与堆栈) 14 | 5. [解析字符串](#5-解析字符串) 15 | 6. [总结和练习](#6-总结和练习) 16 | 7. [参考](#7-参考) 17 | 8. [常见问题](#8-常见问题) 18 | 19 | ## 1. JSON 字符串语法 20 | 21 | JSON 的字符串语法和 C 语言很相似,都是以双引号把字符括起来,如 `"Hello"`。但字符串采用了双引号作分隔,那么怎样可以在字符串中插入一个双引号? 把 `a"b` 写成 `"a"b"` 肯定不行,都不知道那里是字符串的结束了。因此,我们需要引入转义字符(escape character),C 语言和 JSON 都使用 `\`(反斜线)作为转义字符,那么 `"` 在字符串中就表示为 `\"`,`a"b` 的 JSON 字符串则写成 `"a\"b"`。如以下的字符串语法所示,JSON 共支持 9 种转义序列: 22 | 23 | ~~~ 24 | string = quotation-mark *char quotation-mark 25 | char = unescaped / 26 | escape ( 27 | %x22 / ; " quotation mark U+0022 28 | %x5C / ; \ reverse solidus U+005C 29 | %x2F / ; / solidus U+002F 30 | %x62 / ; b backspace U+0008 31 | %x66 / ; f form feed U+000C 32 | %x6E / ; n line feed U+000A 33 | %x72 / ; r carriage return U+000D 34 | %x74 / ; t tab U+0009 35 | %x75 4HEXDIG ) ; uXXXX U+XXXX 36 | escape = %x5C ; \ 37 | quotation-mark = %x22 ; " 38 | unescaped = %x20-21 / %x23-5B / %x5D-10FFFF 39 | ~~~ 40 | 41 | 简单翻译一下,JSON 字符串是由前后两个双引号夹着零至多个字符。字符分为无转义字符或转义序列。转义序列有 9 种,都是以反斜线开始,如常见的 `\n` 代表换行符。比较特殊的是 `\uXXXX`,当中 XXXX 为 16 进位的 UTF-16 编码,本单元将不处理这种转义序列,留待下回分解。 42 | 43 | 无转义字符就是普通的字符,语法中列出了合法的码点范围(码点还是在下单元才介绍)。要注意的是,该范围不包括 0 至 31、双引号和反斜线,这些码点都必须要使用转义方式表示。 44 | 45 | ## 2. 字符串表示 46 | 47 | 在 C 语言中,字符串一般表示为空结尾字符串(null-terminated string),即以空字符(`'\0'`)代表字符串的结束。然而,JSON 字符串是允许含有空字符的,例如这个 JSON `"Hello\u0000World"` 就是单个字符串,解析后为11个字符。如果纯粹使用空结尾字符串来表示 JSON 解析后的结果,就没法处理空字符。 48 | 49 | 因此,我们可以分配内存来储存解析后的字符,以及记录字符的数目(即字符串长度)。由于大部分 C 程序都假设字符串是空结尾字符串,我们还是在最后加上一个空字符,那么不需处理 `\u0000` 这种字符的应用可以简单地把它当作是空结尾字符串。 50 | 51 | 了解需求后,我们考虑实现。`lept_value` 事实上是一种变体类型(variant type),我们通过 `type` 来决定它现时是哪种类型,而这也决定了哪些成员是有效的。首先我们简单地在这个结构中加入两个成员: 52 | 53 | ~~~c 54 | typedef struct { 55 | char* s; 56 | size_t len; 57 | double n; 58 | lept_type type; 59 | }lept_value; 60 | ~~~ 61 | 62 | 然而我们知道,一个值不可能同时为数字和字符串,因此我们可使用 C 语言的 `union` 来节省内存: 63 | 64 | ~~~c 65 | typedef struct { 66 | union { 67 | struct { char* s; size_t len; }s; /* string */ 68 | double n; /* number */ 69 | }u; 70 | lept_type type; 71 | }lept_value; 72 | ~~~ 73 | 74 | 这两种设计在 32 位平台时的内存布局如下,可看出右方使用 `union` 的能省下内存。 75 | 76 | ![union_layout](images/union_layout.png) 77 | 78 | 我们要把之前的 `v->n` 改成 `v->u.n`。而要访问字符串的数据,则要使用 `v->u.s.s` 和 `v->u.s.len`。这种写法比较麻烦吧,其实 C11 新增了匿名 struct/union 语法,就可以采用 `v->n`、`v->s`、`v->len` 来作访问。 79 | 80 | ## 3. 内存管理 81 | 82 | 由于字符串的长度不是固定的,我们要动态分配内存。为简单起见,我们使用标准库 `` 中的 `malloc()`、`realloc()` 和 `free()` 来分配/释放内存。 83 | 84 | 当设置一个值为字符串时,我们需要把参数中的字符串复制一份: 85 | 86 | ~~~c 87 | void lept_set_string(lept_value* v, const char* s, size_t len) { 88 | assert(v != NULL && (s != NULL || len == 0)); 89 | lept_free(v); 90 | v->u.s.s = (char*)malloc(len + 1); 91 | memcpy(v->u.s.s, s, len); 92 | v->u.s.s[len] = '\0'; 93 | v->u.s.len = len; 94 | v->type = LEPT_STRING; 95 | } 96 | ~~~ 97 | 98 | 断言中的条件是,非空指针(有具体的字符串)或是零长度的字符串都是合法的。 99 | 100 | 注意,在设置这个 `v` 之前,我们需要先调用 `lept_free(v)` 去清空 `v` 可能分配到的内存。例如原来已有一字符串,我们要先把它释放。然后就是简单地用 `malloc()` 分配及用 `memcpy()` 复制,并补上结尾空字符。`malloc(len + 1)` 中的 1 是因为结尾空字符。 101 | 102 | 那么,再看看 `lept_free()`: 103 | 104 | ~~~c 105 | void lept_free(lept_value* v) { 106 | assert(v != NULL); 107 | if (v->type == LEPT_STRING) 108 | free(v->u.s.s); 109 | v->type = LEPT_NULL; 110 | } 111 | ~~~ 112 | 113 | 现时仅当值是字符串类型,我们才要处理,之后我们还要加上对数组及对象的释放。`lept_free(v)` 之后,会把它的类型变成 null。这个设计能避免重复释放。 114 | 115 | 但也由于我们会检查 `v` 的类型,在调用所有访问函数之前,我们必须初始化该类型。所以我们加入 `lept_init(v)`,因非常简单我们用宏实现: 116 | 117 | ~~~c 118 | #define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) 119 | ~~~ 120 | 121 | 用上 `do { ... } while(0)` 是为了把表达式转为语句,模仿无返回值的函数。 122 | 123 | 其实在前两个单元中,我们只提供读取值的 API,没有写入的 API,就是因为写入时我们还要考虑释放内存。我们在本单元中把它们补全: 124 | 125 | ~~~c 126 | #define lept_set_null(v) lept_free(v) 127 | 128 | int lept_get_boolean(const lept_value* v); 129 | void lept_set_boolean(lept_value* v, int b); 130 | 131 | double lept_get_number(const lept_value* v); 132 | void lept_set_number(lept_value* v, double n); 133 | 134 | const char* lept_get_string(const lept_value* v); 135 | size_t lept_get_string_length(const lept_value* v); 136 | void lept_set_string(lept_value* v, const char* s, size_t len); 137 | ~~~ 138 | 139 | 由于 `lept_free()` 实际上也会把 `v` 变成 null 值,我们只用一个宏来提供 `lept_set_null()` 这个 API。 140 | 141 | 应用方的代码在调用 `lept_parse()` 之后,最终也应该调用 `lept_free()` 去释放内存。我们把之前的单元测试也加入此调用。 142 | 143 | 如果不使用 `lept_parse()`,我们需要初始化值,那么就像以下的单元测试,先 `lept_init()`,最后 `lept_free()`。 144 | 145 | ~~~c 146 | static void test_access_string() { 147 | lept_value v; 148 | lept_init(&v); 149 | lept_set_string(&v, "", 0); 150 | EXPECT_EQ_STRING("", lept_get_string(&v), lept_get_string_length(&v)); 151 | lept_set_string(&v, "Hello", 5); 152 | EXPECT_EQ_STRING("Hello", lept_get_string(&v), lept_get_string_length(&v)); 153 | lept_free(&v); 154 | } 155 | ~~~ 156 | 157 | ## 4. 缓冲区与堆栈 158 | 159 | 我们解析字符串(以及之后的数组、对象)时,需要把解析的结果先储存在一个临时的缓冲区,最后再用 `lept_set_string()` 把缓冲区的结果设进值之中。在完成解析一个字符串之前,这个缓冲区的大小是不能预知的。因此,我们可以采用动态数组(dynamic array)这种数据结构,即数组空间不足时,能自动扩展。C++ 标准库的 `std::vector` 也是一种动态数组。 160 | 161 | 如果每次解析字符串时,都重新建一个动态数组,那么是比较耗时的。我们可以重用这个动态数组,每次解析 JSON 时就只需要创建一个。而且我们将会发现,无论是解析字符串、数组或对象,我们也只需要以先进后出的方式访问这个动态数组。换句话说,我们需要一个动态的堆栈(stack)数据结构。 162 | 163 | 我们把一个动态堆栈的数据放进 `lept_context` 里: 164 | 165 | ~~~c 166 | typedef struct { 167 | const char* json; 168 | char* stack; 169 | size_t size, top; 170 | }lept_context; 171 | ~~~ 172 | 173 | 当中 `size` 是当前的堆栈容量,`top` 是栈顶的位置(由于我们会扩展 `stack`,所以不要把 `top` 用指针形式存储)。 174 | 175 | 然后,我们在创建 `lept_context` 的时候初始化 `stack` 并最终释放内存: 176 | 177 | ~~~c 178 | int lept_parse(lept_value* v, const char* json) { 179 | lept_context c; 180 | int ret; 181 | assert(v != NULL); 182 | c.json = json; 183 | c.stack = NULL; /* <- */ 184 | c.size = c.top = 0; /* <- */ 185 | lept_init(v); 186 | lept_parse_whitespace(&c); 187 | if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { 188 | /* ... */ 189 | } 190 | assert(c.top == 0); /* <- */ 191 | free(c.stack); /* <- */ 192 | return ret; 193 | } 194 | ~~~ 195 | 196 | 在释放时,加入了断言确保所有数据都被弹出。 197 | 198 | 然后,我们实现堆栈的压入及弹出操作。和普通的堆栈不一样,我们这个堆栈是以字节储存的。每次可要求压入任意大小的数据,它会返回数据起始的指针(会 C++ 的同学可再参考[1]): 199 | 200 | ~~~c 201 | #ifndef LEPT_PARSE_STACK_INIT_SIZE 202 | #define LEPT_PARSE_STACK_INIT_SIZE 256 203 | #endif 204 | 205 | static void* lept_context_push(lept_context* c, size_t size) { 206 | void* ret; 207 | assert(size > 0); 208 | if (c->top + size >= c->size) { 209 | if (c->size == 0) 210 | c->size = LEPT_PARSE_STACK_INIT_SIZE; 211 | while (c->top + size >= c->size) 212 | c->size += c->size >> 1; /* c->size * 1.5 */ 213 | c->stack = (char*)realloc(c->stack, c->size); 214 | } 215 | ret = c->stack + c->top; 216 | c->top += size; 217 | return ret; 218 | } 219 | 220 | static void* lept_context_pop(lept_context* c, size_t size) { 221 | assert(c->top >= size); 222 | return c->stack + (c->top -= size); 223 | } 224 | ~~~ 225 | 226 | 压入时若空间不足,便回以 1.5 倍大小扩展。为什么是 1.5 倍而不是两倍?可参考我在 [STL 的 vector 有哪些封装上的技巧?](https://www.zhihu.com/question/25079705/answer/30030883) 的答案。 227 | 228 | 注意到这里使用了 [`realloc()`](https://en.cppreference.com/w/c/memory/realloc) 来重新分配内存,`c->stack` 在初始化时为 `NULL`,`realloc(NULL, size)` 的行为是等价于 `malloc(size)` 的,所以我们不需要为第一次分配内存作特别处理。 229 | 230 | 另外,我们把初始大小以宏 `LEPT_PARSE_STACK_INIT_SIZE` 的形式定义,使用 `#ifndef X #define X ... #endif` 方式的好处是,使用者可在编译选项中自行设置宏,没设置的话就用缺省值。 231 | 232 | ## 5. 解析字符串 233 | 234 | 有了以上的工具,解析字符串的任务就变得很简单。我们只需要先备份栈顶,然后把解析到的字符压栈,最后计算出长度并一次性把所有字符弹出,再设置至值里便可以。以下是部分实现,没有处理转义和一些不合法字符的校验。 235 | 236 | ~~~c 237 | #define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0) 238 | 239 | static int lept_parse_string(lept_context* c, lept_value* v) { 240 | size_t head = c->top, len; 241 | const char* p; 242 | EXPECT(c, '\"'); 243 | p = c->json; 244 | for (;;) { 245 | char ch = *p++; 246 | switch (ch) { 247 | case '\"': 248 | len = c->top - head; 249 | lept_set_string(v, (const char*)lept_context_pop(c, len), len); 250 | c->json = p; 251 | return LEPT_PARSE_OK; 252 | case '\0': 253 | c->top = head; 254 | return LEPT_PARSE_MISS_QUOTATION_MARK; 255 | default: 256 | PUTC(c, ch); 257 | } 258 | } 259 | } 260 | ~~~ 261 | 262 | ## 6. 总结和练习 263 | 264 | 之前的单元都是固定长度的数据类型(fixed length data type),而字符串类型是可变长度的数据类型(variable length data type),因此本单元花了较多篇幅讲述内存管理和数据结构的设计和实现。字符串的解析相对数字简单,以下的习题难度不高,同学们应该可轻松完成。 265 | 266 | 1. 编写 `lept_get_boolean()` 等访问函数的单元测试,然后实现。 267 | 2. 实现除了 `\u` 以外的转义序列解析,令 `test_parse_string()` 中所有测试通过。 268 | 3. 解决 `test_parse_invalid_string_escape()` 和 `test_parse_invalid_string_char()` 中的失败测试。 269 | 4. 思考如何优化 `test_parse_string()` 的性能,那些优化方法有没有缺点。 270 | 271 | 如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 272 | 273 | ## 7. 参考 274 | 275 | [1] [RapidJSON 代码剖析(一):混合任意类型的堆栈](https://zhuanlan.zhihu.com/p/20029820) 276 | 277 | # 8. 常见问题 278 | 279 | 其他常见问答将会从评论中整理。 280 | -------------------------------------------------------------------------------- /tutorial03_answer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.6) 2 | project (leptjson_test C) 3 | 4 | if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") 6 | endif() 7 | 8 | add_library(leptjson leptjson.c) 9 | add_executable(leptjson_test test.c) 10 | target_link_libraries(leptjson_test leptjson) 11 | -------------------------------------------------------------------------------- /tutorial03_answer/leptjson.c: -------------------------------------------------------------------------------- 1 | #ifdef _WINDOWS 2 | #define _CRTDBG_MAP_ALLOC 3 | #include 4 | #endif 5 | #include "leptjson.h" 6 | #include /* assert() */ 7 | #include /* errno, ERANGE */ 8 | #include /* HUGE_VAL */ 9 | #include /* NULL, malloc(), realloc(), free(), strtod() */ 10 | #include /* memcpy() */ 11 | 12 | #ifndef LEPT_PARSE_STACK_INIT_SIZE 13 | #define LEPT_PARSE_STACK_INIT_SIZE 256 14 | #endif 15 | 16 | #define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) 17 | #define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') 18 | #define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') 19 | #define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0) 20 | 21 | typedef struct { 22 | const char* json; 23 | char* stack; 24 | size_t size, top; 25 | }lept_context; 26 | 27 | static void* lept_context_push(lept_context* c, size_t size) { 28 | void* ret; 29 | assert(size > 0); 30 | if (c->top + size >= c->size) { 31 | if (c->size == 0) 32 | c->size = LEPT_PARSE_STACK_INIT_SIZE; 33 | while (c->top + size >= c->size) 34 | c->size += c->size >> 1; /* c->size * 1.5 */ 35 | c->stack = (char*)realloc(c->stack, c->size); 36 | } 37 | ret = c->stack + c->top; 38 | c->top += size; 39 | return ret; 40 | } 41 | 42 | static void* lept_context_pop(lept_context* c, size_t size) { 43 | assert(c->top >= size); 44 | return c->stack + (c->top -= size); 45 | } 46 | 47 | static void lept_parse_whitespace(lept_context* c) { 48 | const char *p = c->json; 49 | while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') 50 | p++; 51 | c->json = p; 52 | } 53 | 54 | static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { 55 | size_t i; 56 | EXPECT(c, literal[0]); 57 | for (i = 0; literal[i + 1]; i++) 58 | if (c->json[i] != literal[i + 1]) 59 | return LEPT_PARSE_INVALID_VALUE; 60 | c->json += i; 61 | v->type = type; 62 | return LEPT_PARSE_OK; 63 | } 64 | 65 | static int lept_parse_number(lept_context* c, lept_value* v) { 66 | const char* p = c->json; 67 | if (*p == '-') p++; 68 | if (*p == '0') p++; 69 | else { 70 | if (!ISDIGIT1TO9(*p)) return LEPT_PARSE_INVALID_VALUE; 71 | for (p++; ISDIGIT(*p); p++); 72 | } 73 | if (*p == '.') { 74 | p++; 75 | if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; 76 | for (p++; ISDIGIT(*p); p++); 77 | } 78 | if (*p == 'e' || *p == 'E') { 79 | p++; 80 | if (*p == '+' || *p == '-') p++; 81 | if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; 82 | for (p++; ISDIGIT(*p); p++); 83 | } 84 | errno = 0; 85 | v->u.n = strtod(c->json, NULL); 86 | if (errno == ERANGE && (v->u.n == HUGE_VAL || v->u.n == -HUGE_VAL)) 87 | return LEPT_PARSE_NUMBER_TOO_BIG; 88 | v->type = LEPT_NUMBER; 89 | c->json = p; 90 | return LEPT_PARSE_OK; 91 | } 92 | 93 | static int lept_parse_string(lept_context* c, lept_value* v) { 94 | size_t head = c->top, len; 95 | const char* p; 96 | EXPECT(c, '\"'); 97 | p = c->json; 98 | for (;;) { 99 | char ch = *p++; 100 | switch (ch) { 101 | case '\"': 102 | len = c->top - head; 103 | lept_set_string(v, (const char*)lept_context_pop(c, len), len); 104 | c->json = p; 105 | return LEPT_PARSE_OK; 106 | case '\\': 107 | switch (*p++) { 108 | case '\"': PUTC(c, '\"'); break; 109 | case '\\': PUTC(c, '\\'); break; 110 | case '/': PUTC(c, '/' ); break; 111 | case 'b': PUTC(c, '\b'); break; 112 | case 'f': PUTC(c, '\f'); break; 113 | case 'n': PUTC(c, '\n'); break; 114 | case 'r': PUTC(c, '\r'); break; 115 | case 't': PUTC(c, '\t'); break; 116 | default: 117 | c->top = head; 118 | return LEPT_PARSE_INVALID_STRING_ESCAPE; 119 | } 120 | break; 121 | case '\0': 122 | c->top = head; 123 | return LEPT_PARSE_MISS_QUOTATION_MARK; 124 | default: 125 | if ((unsigned char)ch < 0x20) { 126 | c->top = head; 127 | return LEPT_PARSE_INVALID_STRING_CHAR; 128 | } 129 | PUTC(c, ch); 130 | } 131 | } 132 | } 133 | 134 | static int lept_parse_value(lept_context* c, lept_value* v) { 135 | switch (*c->json) { 136 | case 't': return lept_parse_literal(c, v, "true", LEPT_TRUE); 137 | case 'f': return lept_parse_literal(c, v, "false", LEPT_FALSE); 138 | case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL); 139 | default: return lept_parse_number(c, v); 140 | case '"': return lept_parse_string(c, v); 141 | case '\0': return LEPT_PARSE_EXPECT_VALUE; 142 | } 143 | } 144 | 145 | int lept_parse(lept_value* v, const char* json) { 146 | lept_context c; 147 | int ret; 148 | assert(v != NULL); 149 | c.json = json; 150 | c.stack = NULL; 151 | c.size = c.top = 0; 152 | lept_init(v); 153 | lept_parse_whitespace(&c); 154 | if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { 155 | lept_parse_whitespace(&c); 156 | if (*c.json != '\0') { 157 | v->type = LEPT_NULL; 158 | ret = LEPT_PARSE_ROOT_NOT_SINGULAR; 159 | } 160 | } 161 | assert(c.top == 0); 162 | free(c.stack); 163 | return ret; 164 | } 165 | 166 | void lept_free(lept_value* v) { 167 | assert(v != NULL); 168 | if (v->type == LEPT_STRING) 169 | free(v->u.s.s); 170 | v->type = LEPT_NULL; 171 | } 172 | 173 | lept_type lept_get_type(const lept_value* v) { 174 | assert(v != NULL); 175 | return v->type; 176 | } 177 | 178 | int lept_get_boolean(const lept_value* v) { 179 | assert(v != NULL && (v->type == LEPT_TRUE || v->type == LEPT_FALSE)); 180 | return v->type == LEPT_TRUE; 181 | } 182 | 183 | void lept_set_boolean(lept_value* v, int b) { 184 | lept_free(v); 185 | v->type = b ? LEPT_TRUE : LEPT_FALSE; 186 | } 187 | 188 | double lept_get_number(const lept_value* v) { 189 | assert(v != NULL && v->type == LEPT_NUMBER); 190 | return v->u.n; 191 | } 192 | 193 | void lept_set_number(lept_value* v, double n) { 194 | lept_free(v); 195 | v->u.n = n; 196 | v->type = LEPT_NUMBER; 197 | } 198 | 199 | const char* lept_get_string(const lept_value* v) { 200 | assert(v != NULL && v->type == LEPT_STRING); 201 | return v->u.s.s; 202 | } 203 | 204 | size_t lept_get_string_length(const lept_value* v) { 205 | assert(v != NULL && v->type == LEPT_STRING); 206 | return v->u.s.len; 207 | } 208 | 209 | void lept_set_string(lept_value* v, const char* s, size_t len) { 210 | assert(v != NULL && (s != NULL || len == 0)); 211 | lept_free(v); 212 | v->u.s.s = (char*)malloc(len + 1); 213 | memcpy(v->u.s.s, s, len); 214 | v->u.s.s[len] = '\0'; 215 | v->u.s.len = len; 216 | v->type = LEPT_STRING; 217 | } 218 | -------------------------------------------------------------------------------- /tutorial03_answer/leptjson.h: -------------------------------------------------------------------------------- 1 | #ifndef LEPTJSON_H__ 2 | #define LEPTJSON_H__ 3 | 4 | #include /* size_t */ 5 | 6 | typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; 7 | 8 | typedef struct { 9 | union { 10 | struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ 11 | double n; /* number */ 12 | }u; 13 | lept_type type; 14 | }lept_value; 15 | 16 | enum { 17 | LEPT_PARSE_OK = 0, 18 | LEPT_PARSE_EXPECT_VALUE, 19 | LEPT_PARSE_INVALID_VALUE, 20 | LEPT_PARSE_ROOT_NOT_SINGULAR, 21 | LEPT_PARSE_NUMBER_TOO_BIG, 22 | LEPT_PARSE_MISS_QUOTATION_MARK, 23 | LEPT_PARSE_INVALID_STRING_ESCAPE, 24 | LEPT_PARSE_INVALID_STRING_CHAR 25 | }; 26 | 27 | #define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) 28 | 29 | int lept_parse(lept_value* v, const char* json); 30 | 31 | void lept_free(lept_value* v); 32 | 33 | lept_type lept_get_type(const lept_value* v); 34 | 35 | #define lept_set_null(v) lept_free(v) 36 | 37 | int lept_get_boolean(const lept_value* v); 38 | void lept_set_boolean(lept_value* v, int b); 39 | 40 | double lept_get_number(const lept_value* v); 41 | void lept_set_number(lept_value* v, double n); 42 | 43 | const char* lept_get_string(const lept_value* v); 44 | size_t lept_get_string_length(const lept_value* v); 45 | void lept_set_string(lept_value* v, const char* s, size_t len); 46 | 47 | #endif /* LEPTJSON_H__ */ 48 | -------------------------------------------------------------------------------- /tutorial03_answer/test.c: -------------------------------------------------------------------------------- 1 | #ifdef _WINDOWS 2 | #define _CRTDBG_MAP_ALLOC 3 | #include 4 | #endif 5 | #include 6 | #include 7 | #include 8 | #include "leptjson.h" 9 | 10 | static int main_ret = 0; 11 | static int test_count = 0; 12 | static int test_pass = 0; 13 | 14 | #define EXPECT_EQ_BASE(equality, expect, actual, format) \ 15 | do {\ 16 | test_count++;\ 17 | if (equality)\ 18 | test_pass++;\ 19 | else {\ 20 | fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\ 21 | main_ret = 1;\ 22 | }\ 23 | } while(0) 24 | 25 | #define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") 26 | #define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g") 27 | #define EXPECT_EQ_STRING(expect, actual, alength) \ 28 | EXPECT_EQ_BASE(sizeof(expect) - 1 == alength && memcmp(expect, actual, alength) == 0, expect, actual, "%s") 29 | #define EXPECT_TRUE(actual) EXPECT_EQ_BASE((actual) != 0, "true", "false", "%s") 30 | #define EXPECT_FALSE(actual) EXPECT_EQ_BASE((actual) == 0, "false", "true", "%s") 31 | 32 | static void test_parse_null() { 33 | lept_value v; 34 | lept_init(&v); 35 | lept_set_boolean(&v, 0); 36 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null")); 37 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); 38 | lept_free(&v); 39 | } 40 | 41 | static void test_parse_true() { 42 | lept_value v; 43 | lept_init(&v); 44 | lept_set_boolean(&v, 0); 45 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); 46 | EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v)); 47 | lept_free(&v); 48 | } 49 | 50 | static void test_parse_false() { 51 | lept_value v; 52 | lept_init(&v); 53 | lept_set_boolean(&v, 1); 54 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false")); 55 | EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); 56 | lept_free(&v); 57 | } 58 | 59 | #define TEST_NUMBER(expect, json)\ 60 | do {\ 61 | lept_value v;\ 62 | lept_init(&v);\ 63 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ 64 | EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(&v));\ 65 | EXPECT_EQ_DOUBLE(expect, lept_get_number(&v));\ 66 | lept_free(&v);\ 67 | } while(0) 68 | 69 | static void test_parse_number() { 70 | TEST_NUMBER(0.0, "0"); 71 | TEST_NUMBER(0.0, "-0"); 72 | TEST_NUMBER(0.0, "-0.0"); 73 | TEST_NUMBER(1.0, "1"); 74 | TEST_NUMBER(-1.0, "-1"); 75 | TEST_NUMBER(1.5, "1.5"); 76 | TEST_NUMBER(-1.5, "-1.5"); 77 | TEST_NUMBER(3.1416, "3.1416"); 78 | TEST_NUMBER(1E10, "1E10"); 79 | TEST_NUMBER(1e10, "1e10"); 80 | TEST_NUMBER(1E+10, "1E+10"); 81 | TEST_NUMBER(1E-10, "1E-10"); 82 | TEST_NUMBER(-1E10, "-1E10"); 83 | TEST_NUMBER(-1e10, "-1e10"); 84 | TEST_NUMBER(-1E+10, "-1E+10"); 85 | TEST_NUMBER(-1E-10, "-1E-10"); 86 | TEST_NUMBER(1.234E+10, "1.234E+10"); 87 | TEST_NUMBER(1.234E-10, "1.234E-10"); 88 | TEST_NUMBER(0.0, "1e-10000"); /* must underflow */ 89 | 90 | TEST_NUMBER(1.0000000000000002, "1.0000000000000002"); /* the smallest number > 1 */ 91 | TEST_NUMBER( 4.9406564584124654e-324, "4.9406564584124654e-324"); /* minimum denormal */ 92 | TEST_NUMBER(-4.9406564584124654e-324, "-4.9406564584124654e-324"); 93 | TEST_NUMBER( 2.2250738585072009e-308, "2.2250738585072009e-308"); /* Max subnormal double */ 94 | TEST_NUMBER(-2.2250738585072009e-308, "-2.2250738585072009e-308"); 95 | TEST_NUMBER( 2.2250738585072014e-308, "2.2250738585072014e-308"); /* Min normal positive double */ 96 | TEST_NUMBER(-2.2250738585072014e-308, "-2.2250738585072014e-308"); 97 | TEST_NUMBER( 1.7976931348623157e+308, "1.7976931348623157e+308"); /* Max double */ 98 | TEST_NUMBER(-1.7976931348623157e+308, "-1.7976931348623157e+308"); 99 | } 100 | 101 | #define TEST_STRING(expect, json)\ 102 | do {\ 103 | lept_value v;\ 104 | lept_init(&v);\ 105 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ 106 | EXPECT_EQ_INT(LEPT_STRING, lept_get_type(&v));\ 107 | EXPECT_EQ_STRING(expect, lept_get_string(&v), lept_get_string_length(&v));\ 108 | lept_free(&v);\ 109 | } while(0) 110 | 111 | static void test_parse_string() { 112 | TEST_STRING("", "\"\""); 113 | TEST_STRING("Hello", "\"Hello\""); 114 | TEST_STRING("Hello\nWorld", "\"Hello\\nWorld\""); 115 | TEST_STRING("\" \\ / \b \f \n \r \t", "\"\\\" \\\\ \\/ \\b \\f \\n \\r \\t\""); 116 | } 117 | 118 | #define TEST_ERROR(error, json)\ 119 | do {\ 120 | lept_value v;\ 121 | lept_init(&v);\ 122 | v.type = LEPT_FALSE;\ 123 | EXPECT_EQ_INT(error, lept_parse(&v, json));\ 124 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\ 125 | lept_free(&v);\ 126 | } while(0) 127 | 128 | static void test_parse_expect_value() { 129 | TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); 130 | TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); 131 | } 132 | 133 | static void test_parse_invalid_value() { 134 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); 135 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); 136 | 137 | /* invalid number */ 138 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); 139 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); 140 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ 141 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ 142 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); 143 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); 144 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); 145 | TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); 146 | } 147 | 148 | static void test_parse_root_not_singular() { 149 | TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); 150 | 151 | /* invalid number */ 152 | TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ 153 | TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); 154 | TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); 155 | } 156 | 157 | static void test_parse_number_too_big() { 158 | TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); 159 | TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); 160 | } 161 | 162 | static void test_parse_missing_quotation_mark() { 163 | TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\""); 164 | TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\"abc"); 165 | } 166 | 167 | static void test_parse_invalid_string_escape() { 168 | TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\v\""); 169 | TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\'\""); 170 | TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\0\""); 171 | TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\x12\""); 172 | } 173 | 174 | static void test_parse_invalid_string_char() { 175 | TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x01\""); 176 | TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x1F\""); 177 | } 178 | 179 | static void test_access_null() { 180 | lept_value v; 181 | lept_init(&v); 182 | lept_set_string(&v, "a", 1); 183 | lept_set_null(&v); 184 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); 185 | lept_free(&v); 186 | } 187 | 188 | static void test_access_boolean() { 189 | lept_value v; 190 | lept_init(&v); 191 | lept_set_string(&v, "a", 1); 192 | lept_set_boolean(&v, 1); 193 | EXPECT_TRUE(lept_get_boolean(&v)); 194 | lept_set_boolean(&v, 0); 195 | EXPECT_FALSE(lept_get_boolean(&v)); 196 | lept_free(&v); 197 | } 198 | 199 | static void test_access_number() { 200 | lept_value v; 201 | lept_init(&v); 202 | lept_set_string(&v, "a", 1); 203 | lept_set_number(&v, 1234.5); 204 | EXPECT_EQ_DOUBLE(1234.5, lept_get_number(&v)); 205 | lept_free(&v); 206 | } 207 | 208 | static void test_access_string() { 209 | lept_value v; 210 | lept_init(&v); 211 | lept_set_string(&v, "", 0); 212 | EXPECT_EQ_STRING("", lept_get_string(&v), lept_get_string_length(&v)); 213 | lept_set_string(&v, "Hello", 5); 214 | EXPECT_EQ_STRING("Hello", lept_get_string(&v), lept_get_string_length(&v)); 215 | lept_free(&v); 216 | } 217 | 218 | static void test_parse() { 219 | test_parse_null(); 220 | test_parse_true(); 221 | test_parse_false(); 222 | test_parse_number(); 223 | test_parse_string(); 224 | test_parse_expect_value(); 225 | test_parse_invalid_value(); 226 | test_parse_root_not_singular(); 227 | test_parse_number_too_big(); 228 | test_parse_missing_quotation_mark(); 229 | test_parse_invalid_string_escape(); 230 | test_parse_invalid_string_char(); 231 | 232 | test_access_null(); 233 | test_access_boolean(); 234 | test_access_number(); 235 | test_access_string(); 236 | } 237 | 238 | int main() { 239 | #ifdef _WINDOWS 240 | _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); 241 | #endif 242 | test_parse(); 243 | printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); 244 | return main_ret; 245 | } 246 | -------------------------------------------------------------------------------- /tutorial03_answer/tutorial03_answer.md: -------------------------------------------------------------------------------- 1 | # 从零开始的 JSON 库教程(三):解析字符串解答篇 2 | 3 | * Milo Yip 4 | * 2016/9/27 5 | 6 | 本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第三个单元解答编。解答代码位于 [json-tutorial/tutorial03_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial03_answer)。 7 | 8 | ## 1. 访问的单元测试 9 | 10 | 在编写单元测试时,我们故意先把值设为字符串,那么做可以测试设置其他类型时,有没有调用 `lept_free()` 去释放内存。 11 | 12 | ~~~c 13 | static void test_access_boolean() { 14 | lept_value v; 15 | lept_init(&v); 16 | lept_set_string(&v, "a", 1); 17 | lept_set_boolean(&v, 1); 18 | EXPECT_TRUE(lept_get_boolean(&v)); 19 | lept_set_boolean(&v, 0); 20 | EXPECT_FALSE(lept_get_boolean(&v)); 21 | lept_free(&v); 22 | } 23 | 24 | static void test_access_number() { 25 | lept_value v; 26 | lept_init(&v); 27 | lept_set_string(&v, "a", 1); 28 | lept_set_number(&v, 1234.5); 29 | EXPECT_EQ_DOUBLE(1234.5, lept_get_number(&v)); 30 | lept_free(&v); 31 | } 32 | ~~~ 33 | 34 | 以下是访问函数的实现: 35 | 36 | ~~~c 37 | int lept_get_boolean(const lept_value* v) { 38 | assert(v != NULL && (v->type == LEPT_TRUE || v->type == LEPT_FALSE)); 39 | return v->type == LEPT_TRUE; 40 | } 41 | 42 | void lept_set_boolean(lept_value* v, int b) { 43 | lept_free(v); 44 | v->type = b ? LEPT_TRUE : LEPT_FALSE; 45 | } 46 | 47 | double lept_get_number(const lept_value* v) { 48 | assert(v != NULL && v->type == LEPT_NUMBER); 49 | return v->u.n; 50 | } 51 | 52 | void lept_set_number(lept_value* v, double n) { 53 | lept_free(v); 54 | v->u.n = n; 55 | v->type = LEPT_NUMBER; 56 | } 57 | ~~~ 58 | 59 | 那问题是,如果我们没有调用 `lept_free()`,怎样能发现这些内存泄漏? 60 | 61 | ## 1A. Windows 下的内存泄漏检测方法 62 | 63 | 在 Windows 下,可使用 Visual C++ 的 [C Runtime Library(CRT) 检测内存泄漏](https://msdn.microsoft.com/zh-cn/library/x98tx3cf.aspx)。 64 | 65 | 首先,我们在两个 .c 文件首行插入这一段代码: 66 | 67 | ~~~c 68 | #ifdef _WINDOWS 69 | #define _CRTDBG_MAP_ALLOC 70 | #include 71 | #endif 72 | ~~~ 73 | 74 | 并在 `main()` 开始位置插入: 75 | 76 | ~~~c 77 | int main() { 78 | #ifdef _WINDOWS 79 | _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); 80 | #endif 81 | ~~~ 82 | 83 | 在 Debug 配置下按 F5 生成、开始调试程序,没有任何异样。 84 | 85 | 然后,我们删去 `lept_set_boolean()` 中的 `lept_free(v)`: 86 | 87 | ~~~c 88 | void lept_set_boolean(lept_value* v, int b) { 89 | /* lept_free(v); */ 90 | v->type = b ? LEPT_TRUE : LEPT_FALSE; 91 | } 92 | ~~~ 93 | 94 | 再次按 F5 生成、开始调试程序,在输出会看到内存泄漏信息: 95 | 96 | ~~~ 97 | Detected memory leaks! 98 | Dumping objects -> 99 | C:\GitHub\json-tutorial\tutorial03_answer\leptjson.c(212) : {79} normal block at 0x013D9868, 2 bytes long. 100 | Data: 61 00 101 | Object dump complete. 102 | ~~~ 103 | 104 | 这正是我们在单元测试中,先设置字符串,然后设布尔值时没释放字符串所分配的内存。比较麻烦的是,它没有显示调用堆栈。从输出信息中 `... {79} ...` 我们知道是第 79 次分配的内存做成问题,我们可以加上 `_CrtSetBreakAlloc(79);` 来调试,那么它便会在第 79 次时中断于分配调用的位置,那时候就能从调用堆栈去找出来龙去脉。 105 | 106 | ## 1B. Linux/OSX 下的内存泄漏检测方法 107 | 108 | 在 Linux、OS X 下,我们可以使用 [valgrind](https://valgrind.org/) 工具(用 `apt-get install valgrind`、 `brew install valgrind`)。我们完全不用修改代码,只要在命令行执行: 109 | 110 | ~~~ 111 | $ valgrind --leak-check=full ./leptjson_test 112 | ==22078== Memcheck, a memory error detector 113 | ==22078== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. 114 | ==22078== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info 115 | ==22078== Command: ./leptjson_test 116 | ==22078== 117 | --22078-- run: /usr/bin/dsymutil "./leptjson_test" 118 | 160/160 (100.00%) passed 119 | ==22078== 120 | ==22078== HEAP SUMMARY: 121 | ==22078== in use at exit: 27,728 bytes in 209 blocks 122 | ==22078== total heap usage: 301 allocs, 92 frees, 34,966 bytes allocated 123 | ==22078== 124 | ==22078== 2 bytes in 1 blocks are definitely lost in loss record 1 of 79 125 | ==22078== at 0x100012EBB: malloc (in /usr/local/Cellar/valgrind/3.11.0/lib/valgrind/vgpreload_memcheck-amd64-darwin.so) 126 | ==22078== by 0x100008F36: lept_set_string (leptjson.c:208) 127 | ==22078== by 0x100008415: test_access_boolean (test.c:187) 128 | ==22078== by 0x100001849: test_parse (test.c:229) 129 | ==22078== by 0x1000017A3: main (test.c:235) 130 | ==22078== 131 | ... 132 | ~~~ 133 | 134 | 它发现了在 `test_access_boolean()` 中,由 `lept_set_string()` 分配的 2 个字节(`"a"`)泄漏了。 135 | 136 | Valgrind 还有很多功能,例如可以发现未初始化变量。我们若在应用程序或测试程序中,忘了调用 `lept_init(&v)`,那么 `v.type` 的值没被初始化,其值是不确定的(indeterministic),一些函数如果读取那个值就会出现问题: 137 | 138 | ~~~c 139 | static void test_access_boolean() { 140 | lept_value v; 141 | /* lept_init(&v); */ 142 | lept_set_string(&v, "a", 1); 143 | ... 144 | } 145 | ~~~ 146 | 147 | 这种错误有时候测试时能正确运行(刚好 `v.type` 被设为 `0`),使我们误以为程序正确,而在发布后一些机器上却可能崩溃。这种误以为正确的假像是很危险的,我们可利用 valgrind 能自动测出来: 148 | 149 | ~~~ 150 | $ valgrind --leak-check=full ./leptjson_test 151 | ... 152 | ==22174== Conditional jump or move depends on uninitialised value(s) 153 | ==22174== at 0x100008B5D: lept_free (leptjson.c:164) 154 | ==22174== by 0x100008F26: lept_set_string (leptjson.c:207) 155 | ==22174== by 0x1000083FE: test_access_boolean (test.c:187) 156 | ==22174== by 0x100001839: test_parse (test.c:229) 157 | ==22174== by 0x100001793: main (test.c:235) 158 | ==22174== 159 | ~~~ 160 | 161 | 它发现 `lept_free()` 中依靠了一个未初始化的值来跳转,就是 `v.type`,而错误是沿自 `test_access_boolean()`。 162 | 163 | 编写单元测试时,应考虑哪些执行次序会有机会出错,例如内存相关的错误。然后我们可以利用 TDD 的步骤,先令测试失败(以内存工具检测),修正代码,再确认测试是否成功。 164 | 165 | ## 2. 转义序列的解析 166 | 167 | 转义序列的解析很直观,对其他不合法的字符返回 `LEPT_PARSE_INVALID_STRING_ESCAPE`: 168 | 169 | ~~~c 170 | static int lept_parse_string(lept_context* c, lept_value* v) { 171 | /* ... */ 172 | for (;;) { 173 | char ch = *p++; 174 | switch (ch) { 175 | /* ... */ 176 | case '\\': 177 | switch (*p++) { 178 | case '\"': PUTC(c, '\"'); break; 179 | case '\\': PUTC(c, '\\'); break; 180 | case '/': PUTC(c, '/' ); break; 181 | case 'b': PUTC(c, '\b'); break; 182 | case 'f': PUTC(c, '\f'); break; 183 | case 'n': PUTC(c, '\n'); break; 184 | case 'r': PUTC(c, '\r'); break; 185 | case 't': PUTC(c, '\t'); break; 186 | default: 187 | c->top = head; 188 | return LEPT_PARSE_INVALID_STRING_ESCAPE; 189 | } 190 | break; 191 | /* ... */ 192 | } 193 | } 194 | } 195 | ~~~ 196 | 197 | ## 3. 不合法的字符串 198 | 199 | 上面已解决不合法转义,余下部分的唯一难度,是要从语法中知道哪些是不合法字符: 200 | 201 | ~~~ 202 | unescaped = %x20-21 / %x23-5B / %x5D-10FFFF 203 | ~~~ 204 | 205 | 当中空缺的 %x22 是双引号,%x5C 是反斜线,都已经处理。所以不合法的字符是 %x00 至 %x1F。我们简单地在 default 里处理: 206 | 207 | ~~~c 208 | /* ... */ 209 | default: 210 | if ((unsigned char)ch < 0x20) { 211 | c->top = head; 212 | return LEPT_PARSE_INVALID_STRING_CHAR; 213 | } 214 | PUTC(c, ch); 215 | /* ... */ 216 | ~~~ 217 | 218 | 注意到 `char` 带不带符号,是实现定义的。如果编译器定义 `char` 为带符号的话,`(unsigned char)ch >= 0x80` 的字符,都会变成负数,并产生 `LEPT_PARSE_INVALID_STRING_CHAR` 错误。我们现时还没有测试 ASCII 以外的字符,所以有没有转型至不带符号都不影响,但下一单元开始处理 Unicode 的时候就要考虑了。 219 | 220 | ## 4. 性能优化的思考 221 | 222 | 这是本教程第一次的开放式问题,没有标准答案。以下列出一些我想到的。 223 | 224 | 1. 如果整个字符串都没有转义符,我们不就是把字符复制了两次?第一次是从 `json` 到 `stack`,第二次是从 `stack` 到 `v->u.s.s`。我们可以在 `json` 扫描 `'\0'`、`'\"'` 和 `'\\'` 3 个字符( `ch < 0x20` 还是要检查),直至它们其中一个出现,才开始用现在的解析方法。这样做的话,前半没转义的部分可以只复制一次。缺点是,代码变得复杂一些,我们也不能使用 `lept_set_string()`。 225 | 2. 对于扫描没转义部分,我们可考虑用 SIMD 加速,如 [RapidJSON 代码剖析(二):使用 SSE4.2 优化字符串扫描](https://zhuanlan.zhihu.com/p/20037058) 的做法。这类底层优化的缺点是不跨平台,需要设置编译选项等。 226 | 3. 在 gcc/clang 上使用 `__builtin_expect()` 指令来处理低概率事件,例如需要对每个字符做 `LEPT_PARSE_INVALID_STRING_CHAR` 检测,我们可以假设出现不合法字符是低概率事件,然后用这个指令告之编译器,那么编译器可能可生成较快的代码。然而,这类做法明显是不跨编译器,甚至是某个版本后的 gcc 才支持。 227 | 228 | ## 5. 总结 229 | 230 | 本解答篇除了给出一些建议方案,也介绍了内存泄漏的检测方法。JSON 字符串本身的语法并不复杂,但它需要相关的内存分配与数据结构的设计,还好这些设计都能用于之后的数组和对象类型。下一单元专门针对 Unicode,这部分也是许多 JSON 库没有妥善处理的地方。 231 | 232 | 如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 233 | -------------------------------------------------------------------------------- /tutorial04/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.6) 2 | project (leptjson_test C) 3 | 4 | if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") 6 | endif() 7 | 8 | add_library(leptjson leptjson.c) 9 | add_executable(leptjson_test test.c) 10 | target_link_libraries(leptjson_test leptjson) 11 | -------------------------------------------------------------------------------- /tutorial04/images/Utf8webgrowth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/json-tutorial/645d80e849137c85e5a9e54982a9fb50a462e5ea/tutorial04/images/Utf8webgrowth.png -------------------------------------------------------------------------------- /tutorial04/leptjson.c: -------------------------------------------------------------------------------- 1 | #ifdef _WINDOWS 2 | #define _CRTDBG_MAP_ALLOC 3 | #include 4 | #endif 5 | #include "leptjson.h" 6 | #include /* assert() */ 7 | #include /* errno, ERANGE */ 8 | #include /* HUGE_VAL */ 9 | #include /* NULL, malloc(), realloc(), free(), strtod() */ 10 | #include /* memcpy() */ 11 | 12 | #ifndef LEPT_PARSE_STACK_INIT_SIZE 13 | #define LEPT_PARSE_STACK_INIT_SIZE 256 14 | #endif 15 | 16 | #define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) 17 | #define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') 18 | #define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') 19 | #define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0) 20 | 21 | typedef struct { 22 | const char* json; 23 | char* stack; 24 | size_t size, top; 25 | }lept_context; 26 | 27 | static void* lept_context_push(lept_context* c, size_t size) { 28 | void* ret; 29 | assert(size > 0); 30 | if (c->top + size >= c->size) { 31 | if (c->size == 0) 32 | c->size = LEPT_PARSE_STACK_INIT_SIZE; 33 | while (c->top + size >= c->size) 34 | c->size += c->size >> 1; /* c->size * 1.5 */ 35 | c->stack = (char*)realloc(c->stack, c->size); 36 | } 37 | ret = c->stack + c->top; 38 | c->top += size; 39 | return ret; 40 | } 41 | 42 | static void* lept_context_pop(lept_context* c, size_t size) { 43 | assert(c->top >= size); 44 | return c->stack + (c->top -= size); 45 | } 46 | 47 | static void lept_parse_whitespace(lept_context* c) { 48 | const char *p = c->json; 49 | while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') 50 | p++; 51 | c->json = p; 52 | } 53 | 54 | static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { 55 | size_t i; 56 | EXPECT(c, literal[0]); 57 | for (i = 0; literal[i + 1]; i++) 58 | if (c->json[i] != literal[i + 1]) 59 | return LEPT_PARSE_INVALID_VALUE; 60 | c->json += i; 61 | v->type = type; 62 | return LEPT_PARSE_OK; 63 | } 64 | 65 | static int lept_parse_number(lept_context* c, lept_value* v) { 66 | const char* p = c->json; 67 | if (*p == '-') p++; 68 | if (*p == '0') p++; 69 | else { 70 | if (!ISDIGIT1TO9(*p)) return LEPT_PARSE_INVALID_VALUE; 71 | for (p++; ISDIGIT(*p); p++); 72 | } 73 | if (*p == '.') { 74 | p++; 75 | if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; 76 | for (p++; ISDIGIT(*p); p++); 77 | } 78 | if (*p == 'e' || *p == 'E') { 79 | p++; 80 | if (*p == '+' || *p == '-') p++; 81 | if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; 82 | for (p++; ISDIGIT(*p); p++); 83 | } 84 | errno = 0; 85 | v->u.n = strtod(c->json, NULL); 86 | if (errno == ERANGE && (v->u.n == HUGE_VAL || v->u.n == -HUGE_VAL)) 87 | return LEPT_PARSE_NUMBER_TOO_BIG; 88 | v->type = LEPT_NUMBER; 89 | c->json = p; 90 | return LEPT_PARSE_OK; 91 | } 92 | 93 | static const char* lept_parse_hex4(const char* p, unsigned* u) { 94 | /* \TODO */ 95 | return p; 96 | } 97 | 98 | static void lept_encode_utf8(lept_context* c, unsigned u) { 99 | /* \TODO */ 100 | } 101 | 102 | #define STRING_ERROR(ret) do { c->top = head; return ret; } while(0) 103 | 104 | static int lept_parse_string(lept_context* c, lept_value* v) { 105 | size_t head = c->top, len; 106 | unsigned u; 107 | const char* p; 108 | EXPECT(c, '\"'); 109 | p = c->json; 110 | for (;;) { 111 | char ch = *p++; 112 | switch (ch) { 113 | case '\"': 114 | len = c->top - head; 115 | lept_set_string(v, (const char*)lept_context_pop(c, len), len); 116 | c->json = p; 117 | return LEPT_PARSE_OK; 118 | case '\\': 119 | switch (*p++) { 120 | case '\"': PUTC(c, '\"'); break; 121 | case '\\': PUTC(c, '\\'); break; 122 | case '/': PUTC(c, '/' ); break; 123 | case 'b': PUTC(c, '\b'); break; 124 | case 'f': PUTC(c, '\f'); break; 125 | case 'n': PUTC(c, '\n'); break; 126 | case 'r': PUTC(c, '\r'); break; 127 | case 't': PUTC(c, '\t'); break; 128 | case 'u': 129 | if (!(p = lept_parse_hex4(p, &u))) 130 | STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); 131 | /* \TODO surrogate handling */ 132 | lept_encode_utf8(c, u); 133 | break; 134 | default: 135 | STRING_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE); 136 | } 137 | break; 138 | case '\0': 139 | STRING_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK); 140 | default: 141 | if ((unsigned char)ch < 0x20) 142 | STRING_ERROR(LEPT_PARSE_INVALID_STRING_CHAR); 143 | PUTC(c, ch); 144 | } 145 | } 146 | } 147 | 148 | static int lept_parse_value(lept_context* c, lept_value* v) { 149 | switch (*c->json) { 150 | case 't': return lept_parse_literal(c, v, "true", LEPT_TRUE); 151 | case 'f': return lept_parse_literal(c, v, "false", LEPT_FALSE); 152 | case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL); 153 | default: return lept_parse_number(c, v); 154 | case '"': return lept_parse_string(c, v); 155 | case '\0': return LEPT_PARSE_EXPECT_VALUE; 156 | } 157 | } 158 | 159 | int lept_parse(lept_value* v, const char* json) { 160 | lept_context c; 161 | int ret; 162 | assert(v != NULL); 163 | c.json = json; 164 | c.stack = NULL; 165 | c.size = c.top = 0; 166 | lept_init(v); 167 | lept_parse_whitespace(&c); 168 | if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { 169 | lept_parse_whitespace(&c); 170 | if (*c.json != '\0') { 171 | v->type = LEPT_NULL; 172 | ret = LEPT_PARSE_ROOT_NOT_SINGULAR; 173 | } 174 | } 175 | assert(c.top == 0); 176 | free(c.stack); 177 | return ret; 178 | } 179 | 180 | void lept_free(lept_value* v) { 181 | assert(v != NULL); 182 | if (v->type == LEPT_STRING) 183 | free(v->u.s.s); 184 | v->type = LEPT_NULL; 185 | } 186 | 187 | lept_type lept_get_type(const lept_value* v) { 188 | assert(v != NULL); 189 | return v->type; 190 | } 191 | 192 | int lept_get_boolean(const lept_value* v) { 193 | assert(v != NULL && (v->type == LEPT_TRUE || v->type == LEPT_FALSE)); 194 | return v->type == LEPT_TRUE; 195 | } 196 | 197 | void lept_set_boolean(lept_value* v, int b) { 198 | lept_free(v); 199 | v->type = b ? LEPT_TRUE : LEPT_FALSE; 200 | } 201 | 202 | double lept_get_number(const lept_value* v) { 203 | assert(v != NULL && v->type == LEPT_NUMBER); 204 | return v->u.n; 205 | } 206 | 207 | void lept_set_number(lept_value* v, double n) { 208 | lept_free(v); 209 | v->u.n = n; 210 | v->type = LEPT_NUMBER; 211 | } 212 | 213 | const char* lept_get_string(const lept_value* v) { 214 | assert(v != NULL && v->type == LEPT_STRING); 215 | return v->u.s.s; 216 | } 217 | 218 | size_t lept_get_string_length(const lept_value* v) { 219 | assert(v != NULL && v->type == LEPT_STRING); 220 | return v->u.s.len; 221 | } 222 | 223 | void lept_set_string(lept_value* v, const char* s, size_t len) { 224 | assert(v != NULL && (s != NULL || len == 0)); 225 | lept_free(v); 226 | v->u.s.s = (char*)malloc(len + 1); 227 | memcpy(v->u.s.s, s, len); 228 | v->u.s.s[len] = '\0'; 229 | v->u.s.len = len; 230 | v->type = LEPT_STRING; 231 | } 232 | -------------------------------------------------------------------------------- /tutorial04/leptjson.h: -------------------------------------------------------------------------------- 1 | #ifndef LEPTJSON_H__ 2 | #define LEPTJSON_H__ 3 | 4 | #include /* size_t */ 5 | 6 | typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; 7 | 8 | typedef struct { 9 | union { 10 | struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ 11 | double n; /* number */ 12 | }u; 13 | lept_type type; 14 | }lept_value; 15 | 16 | enum { 17 | LEPT_PARSE_OK = 0, 18 | LEPT_PARSE_EXPECT_VALUE, 19 | LEPT_PARSE_INVALID_VALUE, 20 | LEPT_PARSE_ROOT_NOT_SINGULAR, 21 | LEPT_PARSE_NUMBER_TOO_BIG, 22 | LEPT_PARSE_MISS_QUOTATION_MARK, 23 | LEPT_PARSE_INVALID_STRING_ESCAPE, 24 | LEPT_PARSE_INVALID_STRING_CHAR, 25 | LEPT_PARSE_INVALID_UNICODE_HEX, 26 | LEPT_PARSE_INVALID_UNICODE_SURROGATE 27 | }; 28 | 29 | #define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) 30 | 31 | int lept_parse(lept_value* v, const char* json); 32 | 33 | void lept_free(lept_value* v); 34 | 35 | lept_type lept_get_type(const lept_value* v); 36 | 37 | #define lept_set_null(v) lept_free(v) 38 | 39 | int lept_get_boolean(const lept_value* v); 40 | void lept_set_boolean(lept_value* v, int b); 41 | 42 | double lept_get_number(const lept_value* v); 43 | void lept_set_number(lept_value* v, double n); 44 | 45 | const char* lept_get_string(const lept_value* v); 46 | size_t lept_get_string_length(const lept_value* v); 47 | void lept_set_string(lept_value* v, const char* s, size_t len); 48 | 49 | #endif /* LEPTJSON_H__ */ 50 | -------------------------------------------------------------------------------- /tutorial04/tutorial04.md: -------------------------------------------------------------------------------- 1 | # 从零开始的 JSON 库教程(四):Unicode 2 | 3 | * Milo Yip 4 | * 2016/10/2 5 | 6 | 本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第四个单元。代码位于 [json-tutorial/tutorial04](https://github.com/miloyip/json-tutorial/tree/master/tutorial04)。 7 | 8 | 本单元内容: 9 | 10 | 1. [Unicode](#1-unicode) 11 | 2. [需求](#2-需求) 12 | 3. [UTF-8 编码](#3-utf-8-编码) 13 | 4. [实现 `\uXXXX` 解析](#4-实现-uxxxx-解析) 14 | 5. [总结与练习](#5-总结与练习) 15 | 16 | ## 1. Unicode 17 | 18 | 在上一个单元,我们已经能解析「一般」的 JSON 字符串,仅仅没有处理 `\uXXXX` 这种转义序列。为了解析这种序列,我们必须了解有关 Unicode 的基本概念。 19 | 20 | 读者应该知道 ASCII,它是一种字符编码,把 128 个字符映射至整数 0 ~ 127。例如,`1` → 49,`A` → 65,`B` → 66 等等。这种 7-bit 字符编码系统非常简单,在计算机中以一个字节存储一个字符。然而,它仅适合美国英语,甚至一些英语中常用的标点符号、重音符号都不能表示,无法表示各国语言,特别是中日韩语等表意文字。 21 | 22 | 在 Unicode 出现之前,各地区制定了不同的编码系统,如中文主要用 GB 2312 和大五码、日文主要用 JIS 等。这样会造成很多不便,例如一个文本信息很难混合各种语言的文字。 23 | 24 | 因此,在上世纪80年代末,Xerox、Apple 等公司开始研究,是否能制定一套多语言的统一编码系统。后来,多个机构成立了 Unicode 联盟,在 1991 年释出 Unicode 1.0,收录了 24 种语言共 7161 个字符。在四分之一个世纪后的 2016年,Unicode 已释出 9.0 版本,收录 135 种语言共 128237 个字符。 25 | 26 | 这些字符被收录为统一字符集(Universal Coded Character Set, UCS),每个字符映射至一个整数码点(code point),码点的范围是 0 至 0x10FFFF,码点又通常记作 U+XXXX,当中 XXXX 为 16 进位数字。例如 `劲` → U+52B2、`峰` → U+5CF0。很明显,UCS 中的字符无法像 ASCII 般以一个字节存储。 27 | 28 | 因此,Unicode 还制定了各种储存码点的方式,这些方式称为 Unicode 转换格式(Uniform Transformation Format, UTF)。现时流行的 UTF 为 UTF-8、UTF-16 和 UTF-32。每种 UTF 会把一个码点储存为一至多个编码单元(code unit)。例如 UTF-8 的编码单元是 8 位的字节、UTF-16 为 16 位、UTF-32 为 32 位。除 UTF-32 外,UTF-8 和 UTF-16 都是可变长度编码。 29 | 30 | UTF-8 成为现时互联网上最流行的格式,有几个原因: 31 | 32 | 1. 它采用字节为编码单元,不会有字节序(endianness)的问题。 33 | 2. 每个 ASCII 字符只需一个字节去储存。 34 | 3. 如果程序原来是以字节方式储存字符,理论上不需要特别改动就能处理 UTF-8 的数据。 35 | 36 | ## 2. 需求 37 | 38 | 由于 UTF-8 的普及性,大部分的 JSON 也通常会以 UTF-8 存储。我们的 JSON 库也会只支持 UTF-8。(RapidJSON 同时支持 UTF-8、UTF-16LE/BE、UTF-32LE/BE、ASCII。) 39 | 40 | C 标准库没有关于 Unicode 的处理功能(C++11 有),我们会实现 JSON 库所需的字符编码处理功能。 41 | 42 | 对于非转义(unescaped)的字符,只要它们不少于 32(0 ~ 31 是不合法的编码单元),我们可以直接复制至结果,这一点我们稍后再说明。我们假设输入是以合法 UTF-8 编码。 43 | 44 | 而对于 JSON字符串中的 `\uXXXX` 是以 16 进制表示码点 U+0000 至 U+FFFF,我们需要: 45 | 46 | 1. 解析 4 位十六进制整数为码点; 47 | 2. 由于字符串是以 UTF-8 存储,我们要把这个码点编码成 UTF-8。 48 | 49 | 同学可能会发现,4 位的 16 进制数字只能表示 0 至 0xFFFF,但之前我们说 UCS 的码点是从 0 至 0x10FFFF,那怎么能表示多出来的码点? 50 | 51 | 其实,U+0000 至 U+FFFF 这组 Unicode 字符称为基本多文种平面(basic multilingual plane, BMP),还有另外 16 个平面。那么 BMP 以外的字符,JSON 会使用代理对(surrogate pair)表示 `\uXXXX\uYYYY`。在 BMP 中,保留了 2048 个代理码点。如果第一个码点是 U+D800 至 U+DBFF,我们便知道它的代码对的高代理项(high surrogate),之后应该伴随一个 U+DC00 至 U+DFFF 的低代理项(low surrogate)。然后,我们用下列公式把代理对 (H, L) 变换成真实的码点: 52 | 53 | ~~~ 54 | codepoint = 0x10000 + (H − 0xD800) × 0x400 + (L − 0xDC00) 55 | ~~~ 56 | 57 | 举个例子,高音谱号字符 `𝄞` → U+1D11E 不是 BMP 之内的字符。在 JSON 中可写成转义序列 `\uD834\uDD1E`,我们解析第一个 `\uD834` 得到码点 U+D834,我们发现它是 U+D800 至 U+DBFF 内的码点,所以它是高代理项。然后我们解析下一个转义序列 `\uDD1E` 得到码点 U+DD1E,它在 U+DC00 至 U+DFFF 之内,是合法的低代理项。我们计算其码点: 58 | 59 | ~~~ 60 | H = 0xD834, L = 0xDD1E 61 | codepoint = 0x10000 + (H − 0xD800) × 0x400 + (L − 0xDC00) 62 | = 0x10000 + (0xD834 - 0xD800) × 0x400 + (0xDD1E − 0xDC00) 63 | = 0x10000 + 0x34 × 0x400 + 0x11E 64 | = 0x10000 + 0xD000 + 0x11E 65 | = 0x1D11E 66 | ~~~ 67 | 68 | 这样就得出这转义序列的码点,然后我们再把它编码成 UTF-8。如果只有高代理项而欠缺低代理项,或是低代理项不在合法码点范围,我们都返回 `LEPT_PARSE_INVALID_UNICODE_SURROGATE` 错误。如果 `\u` 后不是 4 位十六进位数字,则返回 `LEPT_PARSE_INVALID_UNICODE_HEX` 错误。 69 | 70 | ## 3. UTF-8 编码 71 | 72 | UTF-8 在网页上的使用率势无可挡: 73 | 74 | ![ ](images/Utf8webgrowth.png) 75 | 76 | (图片来自 [Wikipedia Common](https://commons.wikimedia.org/wiki/File:Utf8webgrowth.svg),数据来自 Google 对网页字符编码的统计。) 77 | 78 | 由于我们的 JSON 库也只支持 UTF-8,我们需要把码点编码成 UTF-8。这里简单介绍一下 UTF-8 的编码方式。 79 | 80 | UTF-8 的编码单元为 8 位(1 字节),每个码点编码成 1 至 4 个字节。它的编码方式很简单,按照码点的范围,把码点的二进位分拆成 1 至最多 4 个字节: 81 | 82 | | 码点范围 | 码点位数 | 字节1 | 字节2 | 字节3 | 字节4 | 83 | |:------------------:|:--------:|:--------:|:--------:|:--------:|:--------:| 84 | | U+0000 ~ U+007F | 7 | 0xxxxxxx | 85 | | U+0080 ~ U+07FF | 11 | 110xxxxx | 10xxxxxx | 86 | | U+0800 ~ U+FFFF | 16 | 1110xxxx | 10xxxxxx | 10xxxxxx | 87 | | U+10000 ~ U+10FFFF | 21 | 11110xxx | 10xxxxxx | 10xxxxxx | 10xxxxxx | 88 | 89 | 这个编码方法的好处之一是,码点范围 U+0000 ~ U+007F 编码为一个字节,与 ASCII 编码兼容。这范围的 Unicode 码点也是和 ASCII 字符相同的。因此,一个 ASCII 文本也是一个 UTF-8 文本。 90 | 91 | 我们举一个例子解析多字节的情况,欧元符号 `€` → U+20AC: 92 | 93 | 1. U+20AC 在 U+0800 ~ U+FFFF 的范围内,应编码成 3 个字节。 94 | 2. U+20AC 的二进位为 10000010101100 95 | 3. 3 个字节的情况我们要 16 位的码点,所以在前面补两个 0,成为 0010000010101100 96 | 4. 按上表把二进位分成 3 组:0010, 000010, 101100 97 | 5. 加上每个字节的前缀:11100010, 10000010, 10101100 98 | 6. 用十六进位表示即:0xE2, 0x82, 0xAC 99 | 100 | 对于这例子的范围,对应的 C 代码是这样的: 101 | 102 | ~~~c 103 | if (u >= 0x0800 && u <= 0xFFFF) { 104 | OutputByte(0xE0 | ((u >> 12) & 0xFF)); /* 0xE0 = 11100000 */ 105 | OutputByte(0x80 | ((u >> 6) & 0x3F)); /* 0x80 = 10000000 */ 106 | OutputByte(0x80 | ( u & 0x3F)); /* 0x3F = 00111111 */ 107 | } 108 | ~~~ 109 | 110 | UTF-8 的解码稍复杂一点,但我们的 JSON 库不会校验 JSON 文本是否符合 UTF-8,所以这里也不展开了。 111 | 112 | ## 4. 实现 `\uXXXX` 解析 113 | 114 | 我们只需要在其它转义符的处理中加入对 `\uXXXX` 的处理: 115 | 116 | ~~~c 117 | static int lept_parse_string(lept_context* c, lept_value* v) { 118 | unsigned u; 119 | /* ... */ 120 | for (;;) { 121 | char ch = *p++; 122 | switch (ch) { 123 | /* ... */ 124 | case '\\': 125 | switch (*p++) { 126 | /* ... */ 127 | case 'u': 128 | if (!(p = lept_parse_hex4(p, &u))) 129 | STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); 130 | /* \TODO surrogate handling */ 131 | lept_encode_utf8(c, u); 132 | break; 133 | /* ... */ 134 | } 135 | /* ... */ 136 | } 137 | } 138 | } 139 | ~~~ 140 | 141 | 上面代码的过程很简单,遇到 `\u` 转义时,调用 `lept_parse_hex4()` 解析 4 位十六进数字,存储为码点 `u`。这个函数在成功时返回解析后的文本指针,失败返回 `NULL`。如果失败,就返回 `LEPT_PARSE_INVALID_UNICODE_HEX` 错误。最后,把码点编码成 UTF-8,写进缓冲区。这里没有处理代理对,留作练习。 142 | 143 | 顺带一提,我为 `lept_parse_string()` 做了个简单的重构,把返回错误码的处理抽取为宏: 144 | 145 | ~~~c 146 | #define STRING_ERROR(ret) do { c->top = head; return ret; } while(0) 147 | ~~~ 148 | 149 | ## 5. 总结与练习 150 | 151 | 本单元介绍了 Unicode 的基本知识,同学应该了解到一些常用的 Unicode 术语,如码点、编码单元、UTF-8、代理对等。这次的练习代码只有个空壳,要由同学填充。完成后应该能通过所有单元测试,届时我们的 JSON 字符串解析就完全符合标准了。 152 | 153 | 1. 实现 `lept_parse_hex4()`,不合法的十六进位数返回 `LEPT_PARSE_INVALID_UNICODE_HEX`。 154 | 2. 按第 3 节谈到的 UTF-8 编码原理,实现 `lept_encode_utf8()`。这函数假设码点在正确范围 U+0000 ~ U+10FFFF(用断言检测)。 155 | 3. 加入对代理对的处理,不正确的代理对范围要返回 `LEPT_PARSE_INVALID_UNICODE_SURROGATE` 错误。 156 | 157 | 如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 158 | -------------------------------------------------------------------------------- /tutorial04_answer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.6) 2 | project (leptjson_test C) 3 | 4 | if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") 6 | endif() 7 | 8 | add_library(leptjson leptjson.c) 9 | add_executable(leptjson_test test.c) 10 | target_link_libraries(leptjson_test leptjson) 11 | -------------------------------------------------------------------------------- /tutorial04_answer/leptjson.c: -------------------------------------------------------------------------------- 1 | #ifdef _WINDOWS 2 | #define _CRTDBG_MAP_ALLOC 3 | #include 4 | #endif 5 | #include "leptjson.h" 6 | #include /* assert() */ 7 | #include /* errno, ERANGE */ 8 | #include /* HUGE_VAL */ 9 | #include /* NULL, malloc(), realloc(), free(), strtod() */ 10 | #include /* memcpy() */ 11 | 12 | #ifndef LEPT_PARSE_STACK_INIT_SIZE 13 | #define LEPT_PARSE_STACK_INIT_SIZE 256 14 | #endif 15 | 16 | #define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) 17 | #define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') 18 | #define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') 19 | #define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0) 20 | 21 | typedef struct { 22 | const char* json; 23 | char* stack; 24 | size_t size, top; 25 | }lept_context; 26 | 27 | static void* lept_context_push(lept_context* c, size_t size) { 28 | void* ret; 29 | assert(size > 0); 30 | if (c->top + size >= c->size) { 31 | if (c->size == 0) 32 | c->size = LEPT_PARSE_STACK_INIT_SIZE; 33 | while (c->top + size >= c->size) 34 | c->size += c->size >> 1; /* c->size * 1.5 */ 35 | c->stack = (char*)realloc(c->stack, c->size); 36 | } 37 | ret = c->stack + c->top; 38 | c->top += size; 39 | return ret; 40 | } 41 | 42 | static void* lept_context_pop(lept_context* c, size_t size) { 43 | assert(c->top >= size); 44 | return c->stack + (c->top -= size); 45 | } 46 | 47 | static void lept_parse_whitespace(lept_context* c) { 48 | const char *p = c->json; 49 | while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') 50 | p++; 51 | c->json = p; 52 | } 53 | 54 | static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { 55 | size_t i; 56 | EXPECT(c, literal[0]); 57 | for (i = 0; literal[i + 1]; i++) 58 | if (c->json[i] != literal[i + 1]) 59 | return LEPT_PARSE_INVALID_VALUE; 60 | c->json += i; 61 | v->type = type; 62 | return LEPT_PARSE_OK; 63 | } 64 | 65 | static int lept_parse_number(lept_context* c, lept_value* v) { 66 | const char* p = c->json; 67 | if (*p == '-') p++; 68 | if (*p == '0') p++; 69 | else { 70 | if (!ISDIGIT1TO9(*p)) return LEPT_PARSE_INVALID_VALUE; 71 | for (p++; ISDIGIT(*p); p++); 72 | } 73 | if (*p == '.') { 74 | p++; 75 | if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; 76 | for (p++; ISDIGIT(*p); p++); 77 | } 78 | if (*p == 'e' || *p == 'E') { 79 | p++; 80 | if (*p == '+' || *p == '-') p++; 81 | if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; 82 | for (p++; ISDIGIT(*p); p++); 83 | } 84 | errno = 0; 85 | v->u.n = strtod(c->json, NULL); 86 | if (errno == ERANGE && (v->u.n == HUGE_VAL || v->u.n == -HUGE_VAL)) 87 | return LEPT_PARSE_NUMBER_TOO_BIG; 88 | v->type = LEPT_NUMBER; 89 | c->json = p; 90 | return LEPT_PARSE_OK; 91 | } 92 | 93 | static const char* lept_parse_hex4(const char* p, unsigned* u) { 94 | int i; 95 | *u = 0; 96 | for (i = 0; i < 4; i++) { 97 | char ch = *p++; 98 | *u <<= 4; 99 | if (ch >= '0' && ch <= '9') *u |= ch - '0'; 100 | else if (ch >= 'A' && ch <= 'F') *u |= ch - ('A' - 10); 101 | else if (ch >= 'a' && ch <= 'f') *u |= ch - ('a' - 10); 102 | else return NULL; 103 | } 104 | return p; 105 | } 106 | 107 | static void lept_encode_utf8(lept_context* c, unsigned u) { 108 | if (u <= 0x7F) 109 | PUTC(c, u & 0xFF); 110 | else if (u <= 0x7FF) { 111 | PUTC(c, 0xC0 | ((u >> 6) & 0xFF)); 112 | PUTC(c, 0x80 | ( u & 0x3F)); 113 | } 114 | else if (u <= 0xFFFF) { 115 | PUTC(c, 0xE0 | ((u >> 12) & 0xFF)); 116 | PUTC(c, 0x80 | ((u >> 6) & 0x3F)); 117 | PUTC(c, 0x80 | ( u & 0x3F)); 118 | } 119 | else { 120 | assert(u <= 0x10FFFF); 121 | PUTC(c, 0xF0 | ((u >> 18) & 0xFF)); 122 | PUTC(c, 0x80 | ((u >> 12) & 0x3F)); 123 | PUTC(c, 0x80 | ((u >> 6) & 0x3F)); 124 | PUTC(c, 0x80 | ( u & 0x3F)); 125 | } 126 | } 127 | 128 | #define STRING_ERROR(ret) do { c->top = head; return ret; } while(0) 129 | 130 | static int lept_parse_string(lept_context* c, lept_value* v) { 131 | size_t head = c->top, len; 132 | unsigned u, u2; 133 | const char* p; 134 | EXPECT(c, '\"'); 135 | p = c->json; 136 | for (;;) { 137 | char ch = *p++; 138 | switch (ch) { 139 | case '\"': 140 | len = c->top - head; 141 | lept_set_string(v, (const char*)lept_context_pop(c, len), len); 142 | c->json = p; 143 | return LEPT_PARSE_OK; 144 | case '\\': 145 | switch (*p++) { 146 | case '\"': PUTC(c, '\"'); break; 147 | case '\\': PUTC(c, '\\'); break; 148 | case '/': PUTC(c, '/' ); break; 149 | case 'b': PUTC(c, '\b'); break; 150 | case 'f': PUTC(c, '\f'); break; 151 | case 'n': PUTC(c, '\n'); break; 152 | case 'r': PUTC(c, '\r'); break; 153 | case 't': PUTC(c, '\t'); break; 154 | case 'u': 155 | if (!(p = lept_parse_hex4(p, &u))) 156 | STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); 157 | if (u >= 0xD800 && u <= 0xDBFF) { /* surrogate pair */ 158 | if (*p++ != '\\') 159 | STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); 160 | if (*p++ != 'u') 161 | STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); 162 | if (!(p = lept_parse_hex4(p, &u2))) 163 | STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); 164 | if (u2 < 0xDC00 || u2 > 0xDFFF) 165 | STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); 166 | u = (((u - 0xD800) << 10) | (u2 - 0xDC00)) + 0x10000; 167 | } 168 | lept_encode_utf8(c, u); 169 | break; 170 | default: 171 | STRING_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE); 172 | } 173 | break; 174 | case '\0': 175 | STRING_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK); 176 | default: 177 | if ((unsigned char)ch < 0x20) 178 | STRING_ERROR(LEPT_PARSE_INVALID_STRING_CHAR); 179 | PUTC(c, ch); 180 | } 181 | } 182 | } 183 | 184 | static int lept_parse_value(lept_context* c, lept_value* v) { 185 | switch (*c->json) { 186 | case 't': return lept_parse_literal(c, v, "true", LEPT_TRUE); 187 | case 'f': return lept_parse_literal(c, v, "false", LEPT_FALSE); 188 | case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL); 189 | default: return lept_parse_number(c, v); 190 | case '"': return lept_parse_string(c, v); 191 | case '\0': return LEPT_PARSE_EXPECT_VALUE; 192 | } 193 | } 194 | 195 | int lept_parse(lept_value* v, const char* json) { 196 | lept_context c; 197 | int ret; 198 | assert(v != NULL); 199 | c.json = json; 200 | c.stack = NULL; 201 | c.size = c.top = 0; 202 | lept_init(v); 203 | lept_parse_whitespace(&c); 204 | if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { 205 | lept_parse_whitespace(&c); 206 | if (*c.json != '\0') { 207 | v->type = LEPT_NULL; 208 | ret = LEPT_PARSE_ROOT_NOT_SINGULAR; 209 | } 210 | } 211 | assert(c.top == 0); 212 | free(c.stack); 213 | return ret; 214 | } 215 | 216 | void lept_free(lept_value* v) { 217 | assert(v != NULL); 218 | if (v->type == LEPT_STRING) 219 | free(v->u.s.s); 220 | v->type = LEPT_NULL; 221 | } 222 | 223 | lept_type lept_get_type(const lept_value* v) { 224 | assert(v != NULL); 225 | return v->type; 226 | } 227 | 228 | int lept_get_boolean(const lept_value* v) { 229 | assert(v != NULL && (v->type == LEPT_TRUE || v->type == LEPT_FALSE)); 230 | return v->type == LEPT_TRUE; 231 | } 232 | 233 | void lept_set_boolean(lept_value* v, int b) { 234 | lept_free(v); 235 | v->type = b ? LEPT_TRUE : LEPT_FALSE; 236 | } 237 | 238 | double lept_get_number(const lept_value* v) { 239 | assert(v != NULL && v->type == LEPT_NUMBER); 240 | return v->u.n; 241 | } 242 | 243 | void lept_set_number(lept_value* v, double n) { 244 | lept_free(v); 245 | v->u.n = n; 246 | v->type = LEPT_NUMBER; 247 | } 248 | 249 | const char* lept_get_string(const lept_value* v) { 250 | assert(v != NULL && v->type == LEPT_STRING); 251 | return v->u.s.s; 252 | } 253 | 254 | size_t lept_get_string_length(const lept_value* v) { 255 | assert(v != NULL && v->type == LEPT_STRING); 256 | return v->u.s.len; 257 | } 258 | 259 | void lept_set_string(lept_value* v, const char* s, size_t len) { 260 | assert(v != NULL && (s != NULL || len == 0)); 261 | lept_free(v); 262 | v->u.s.s = (char*)malloc(len + 1); 263 | memcpy(v->u.s.s, s, len); 264 | v->u.s.s[len] = '\0'; 265 | v->u.s.len = len; 266 | v->type = LEPT_STRING; 267 | } 268 | -------------------------------------------------------------------------------- /tutorial04_answer/leptjson.h: -------------------------------------------------------------------------------- 1 | #ifndef LEPTJSON_H__ 2 | #define LEPTJSON_H__ 3 | 4 | #include /* size_t */ 5 | 6 | typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; 7 | 8 | typedef struct { 9 | union { 10 | struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ 11 | double n; /* number */ 12 | }u; 13 | lept_type type; 14 | }lept_value; 15 | 16 | enum { 17 | LEPT_PARSE_OK = 0, 18 | LEPT_PARSE_EXPECT_VALUE, 19 | LEPT_PARSE_INVALID_VALUE, 20 | LEPT_PARSE_ROOT_NOT_SINGULAR, 21 | LEPT_PARSE_NUMBER_TOO_BIG, 22 | LEPT_PARSE_MISS_QUOTATION_MARK, 23 | LEPT_PARSE_INVALID_STRING_ESCAPE, 24 | LEPT_PARSE_INVALID_STRING_CHAR, 25 | LEPT_PARSE_INVALID_UNICODE_HEX, 26 | LEPT_PARSE_INVALID_UNICODE_SURROGATE 27 | }; 28 | 29 | #define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) 30 | 31 | int lept_parse(lept_value* v, const char* json); 32 | 33 | void lept_free(lept_value* v); 34 | 35 | lept_type lept_get_type(const lept_value* v); 36 | 37 | #define lept_set_null(v) lept_free(v) 38 | 39 | int lept_get_boolean(const lept_value* v); 40 | void lept_set_boolean(lept_value* v, int b); 41 | 42 | double lept_get_number(const lept_value* v); 43 | void lept_set_number(lept_value* v, double n); 44 | 45 | const char* lept_get_string(const lept_value* v); 46 | size_t lept_get_string_length(const lept_value* v); 47 | void lept_set_string(lept_value* v, const char* s, size_t len); 48 | 49 | #endif /* LEPTJSON_H__ */ 50 | -------------------------------------------------------------------------------- /tutorial04_answer/tutorial04_answer.md: -------------------------------------------------------------------------------- 1 | # 从零开始的 JSON 库教程(四):Unicode 解答篇 2 | 3 | * Milo Yip 4 | * 2016/10/6 5 | 6 | 本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第四个单元解答篇。解答代码位于 [json-tutorial/tutorial04_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial04_answer)。 7 | 8 | ## 1. 实现 `lept_parse_hex4()` 9 | 10 | 这个函数只是读 4 位 16 进制数字,可以简单地自行实现: 11 | 12 | ~~~c 13 | static const char* lept_parse_hex4(const char* p, unsigned* u) { 14 | int i; 15 | *u = 0; 16 | for (i = 0; i < 4; i++) { 17 | char ch = *p++; 18 | *u <<= 4; 19 | if (ch >= '0' && ch <= '9') *u |= ch - '0'; 20 | else if (ch >= 'A' && ch <= 'F') *u |= ch - ('A' - 10); 21 | else if (ch >= 'a' && ch <= 'f') *u |= ch - ('a' - 10); 22 | else return NULL; 23 | } 24 | return p; 25 | } 26 | ~~~ 27 | 28 | 可能有同学想到用标准库的 [`strtol()`](https://en.cppreference.com/w/c/string/byte/strtol),因为它也能解析 16 进制数字,那么可以简短的写成: 29 | 30 | ~~~c 31 | static const char* lept_parse_hex4(const char* p, unsigned* u) { 32 | char* end; 33 | *u = (unsigned)strtol(p, &end, 16); 34 | return end == p + 4 ? end : NULL; 35 | } 36 | ~~~ 37 | 38 | 但这个实现会错误地接受 `"\u 123"` 这种不合法的 JSON,因为 `strtol()` 会跳过开始的空白。要解决的话,还需要检测第一个字符是否 `[0-9A-Fa-f]`,或者 `!isspace(*p)`。但为了 `strtol()` 做多余的检测,而且自行实现也很简单,我个人会选择首个方案。(前两个单元用 `strtod()` 就没办法,因为它的实现要复杂得多。) 39 | 40 | ## 2. 实现 `lept_encode_utf8()` 41 | 42 | 这个函数只需要根据那个 UTF-8 编码表就可以实现: 43 | 44 | ~~~c 45 | static void lept_encode_utf8(lept_context* c, unsigned u) { 46 | if (u <= 0x7F) 47 | PUTC(c, u & 0xFF); 48 | else if (u <= 0x7FF) { 49 | PUTC(c, 0xC0 | ((u >> 6) & 0xFF)); 50 | PUTC(c, 0x80 | ( u & 0x3F)); 51 | } 52 | else if (u <= 0xFFFF) { 53 | PUTC(c, 0xE0 | ((u >> 12) & 0xFF)); 54 | PUTC(c, 0x80 | ((u >> 6) & 0x3F)); 55 | PUTC(c, 0x80 | ( u & 0x3F)); 56 | } 57 | else { 58 | assert(u <= 0x10FFFF); 59 | PUTC(c, 0xF0 | ((u >> 18) & 0xFF)); 60 | PUTC(c, 0x80 | ((u >> 12) & 0x3F)); 61 | PUTC(c, 0x80 | ((u >> 6) & 0x3F)); 62 | PUTC(c, 0x80 | ( u & 0x3F)); 63 | } 64 | } 65 | ~~~ 66 | 67 | 有同学可能觉得奇怪,最终也是写进一个 `char`,为什么要做 `x & 0xFF` 这种操作呢?这是因为 `u` 是 `unsigned` 类型,一些编译器可能会警告这个转型可能会截断数据。但实际上,配合了范围的检测然后右移之后,可以保证写入的是 0~255 内的值。为了避免一些编译器的警告误判,我们加上 `x & 0xFF`。一般来说,编译器在优化之后,这与操作是会被消去的,不会影响性能。 68 | 69 | 其实超过 1 个字符输出时,可以只调用 1 次 `lept_context_push()`。这里全用 `PUTC()` 只是为了代码看上去简单一点。 70 | 71 | ## 3. 代理对的处理 72 | 73 | 遇到高代理项,就需要把低代理项 `\uxxxx` 也解析进来,然后用这两个项去计算出码点: 74 | 75 | ~~~c 76 | case 'u': 77 | if (!(p = lept_parse_hex4(p, &u))) 78 | STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); 79 | if (u >= 0xD800 && u <= 0xDBFF) { /* surrogate pair */ 80 | if (*p++ != '\\') 81 | STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); 82 | if (*p++ != 'u') 83 | STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); 84 | if (!(p = lept_parse_hex4(p, &u2))) 85 | STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); 86 | if (u2 < 0xDC00 || u2 > 0xDFFF) 87 | STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); 88 | u = (((u - 0xD800) << 10) | (u2 - 0xDC00)) + 0x10000; 89 | } 90 | lept_encode_utf8(c, u); 91 | break; 92 | ~~~ 93 | 94 | ## 4. 总结 95 | 96 | JSON 的字符串解析终于完成了。但更重要的是,同学通过教程和练习后,应该对于 Unicode 和 UTF-8 编码有基本了解。使用 Unicode 标准去处理文本数据已是世界潮流。虽然 C11/C++11 引入了 Unicode 字符串字面量及少量函数,但仍然有很多不足,一般需要借助第三方库。 97 | 98 | 我们在稍后的单元还要处理生成时的 Unicode 问题,接下来我们要继续讨论数组和对象的解析。 99 | 100 | 如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 101 | -------------------------------------------------------------------------------- /tutorial05/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.6) 2 | project (leptjson_test C) 3 | 4 | if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") 6 | endif() 7 | 8 | add_library(leptjson leptjson.c) 9 | add_executable(leptjson_test test.c) 10 | target_link_libraries(leptjson_test leptjson) 11 | -------------------------------------------------------------------------------- /tutorial05/images/makefile: -------------------------------------------------------------------------------- 1 | %.png: %.dot 2 | dot $< -Tpng -o $@ 3 | 4 | DOTFILES = $(basename $(wildcard *.dot)) 5 | all: $(addsuffix .png, $(DOTFILES)) 6 | -------------------------------------------------------------------------------- /tutorial05/images/parse_array01.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=TB 3 | compound=true 4 | fontname="Inconsolata, Consolas" 5 | fontsize=10 6 | margin="0,0" 7 | ranksep=0.3 8 | nodesep=1 9 | penwidth=0.5 10 | 11 | node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, colorscheme=spectral7] 12 | edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] 13 | 14 | { 15 | node [shape=record, style=filled, margin=0.1, height=0.3] 16 | json [fillcolor=3, label="[|\"|a|b|c|\"|,|[|1|,|2|]|,|3|]|\\0"] 17 | stack [fillcolor=4, label=" | | | | | |"] 18 | } 19 | { 20 | node [shape=plaintext, margin=0] 21 | 22 | cjson [label="c->json"] 23 | ctop [label="c->top"] 24 | desc [style=solid,label="\l1. lept_parse()\l 2. lept_parse_value() \l 3. lept_parse_array()"] 25 | } 26 | 27 | cjson -> json:j 28 | ctop -> stack:t 29 | json -> desc [style=invis] 30 | } -------------------------------------------------------------------------------- /tutorial05/images/parse_array01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/json-tutorial/645d80e849137c85e5a9e54982a9fb50a462e5ea/tutorial05/images/parse_array01.png -------------------------------------------------------------------------------- /tutorial05/images/parse_array02.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=TB 3 | compound=true 4 | fontname="Inconsolata, Consolas" 5 | fontsize=10 6 | margin="0,0" 7 | ranksep=0.3 8 | nodesep=1 9 | penwidth=0.5 10 | 11 | node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, colorscheme=spectral7] 12 | edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] 13 | 14 | { 15 | node [shape=record, style=filled, margin=0.1, height=0.3] 16 | json [fillcolor=3, label="[|\"|a|b|c|\"|,|[|1|,|2|]|,|3|]|\\0"] 17 | stack [fillcolor=4, label="| | | | | |"] 18 | } 19 | { 20 | node [shape=plaintext, margin=0] 21 | 22 | cjson [label="c->json"] 23 | ctop [label="c->top"] 24 | desc [style=solid,label="\l1. lept_parse()\l 2. lept_parse_value()\l 3. lept_parse_array()\l 4. lept_parse_value()\l 5. lept_parse_string()"] 25 | } 26 | { 27 | node [shape=Mrecord,style=filled] 28 | 29 | e [fillcolor=5,label="{null|}"] 30 | } 31 | 32 | cjson -> json:j 33 | ctop -> stack:t 34 | json -> desc [style=invis] 35 | stack -> e [style=invis] 36 | } -------------------------------------------------------------------------------- /tutorial05/images/parse_array02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/json-tutorial/645d80e849137c85e5a9e54982a9fb50a462e5ea/tutorial05/images/parse_array02.png -------------------------------------------------------------------------------- /tutorial05/images/parse_array03.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=TB 3 | compound=true 4 | fontname="Inconsolata, Consolas" 5 | fontsize=10 6 | margin="0,0" 7 | ranksep=0.3 8 | nodesep=1 9 | penwidth=0.5 10 | 11 | node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, colorscheme=spectral7] 12 | edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] 13 | 14 | { 15 | node [shape=record, style=filled, margin=0.1, height=0.3] 16 | json [fillcolor=3, label="[|\"|a|b|c|\"|,|[|1|,|2|]|,|3|]|\\0"] 17 | stack [fillcolor=4, label="a|b|c|| | |"] 18 | } 19 | { 20 | node [shape=plaintext, margin=0] 21 | 22 | cjson [label="c->json"] 23 | ctop [label="c->top"] 24 | desc [style=solid,label="\l1. lept_parse()\l 2. lept_parse_value()\l 3. lept_parse_array()\l 4. lept_parse_value()\l 5. lept_parse_string()"] 25 | } 26 | 27 | { 28 | node [shape=Mrecord,style=filled] 29 | 30 | e [fillcolor=5,label="{null|}"] 31 | } 32 | 33 | cjson -> json:j 34 | ctop -> stack:t 35 | json -> desc [style=invis] 36 | stack -> e [style=invis] 37 | } -------------------------------------------------------------------------------- /tutorial05/images/parse_array03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/json-tutorial/645d80e849137c85e5a9e54982a9fb50a462e5ea/tutorial05/images/parse_array03.png -------------------------------------------------------------------------------- /tutorial05/images/parse_array04.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=TB 3 | compound=true 4 | fontname="Inconsolata, Consolas" 5 | fontsize=10 6 | margin="0,0" 7 | ranksep=0.3 8 | nodesep=1 9 | penwidth=0.5 10 | 11 | node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, colorscheme=spectral7] 12 | edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] 13 | 14 | { 15 | node [shape=record, style=filled, margin=0.1, height=0.3] 16 | json [fillcolor=3, label="[|\"|a|b|c|\"|,|[|1|,|2|]|,|3|]|\\0"] 17 | stack [fillcolor=4, label=" | | | | | |"] 18 | abc [fillcolor=3, label="a|b|c|\\0"] 19 | } 20 | { 21 | node [shape=plaintext, margin=0] 22 | 23 | cjson [label="c->json"] 24 | ctop [label="c->top"] 25 | desc [style=solid,label="\l1. lept_parse()\l 2. lept_parse_value()\l 3. lept_parse_array()\l 4. lept_parse_value()\l 5. lept_parse_string()"] 26 | } 27 | 28 | { 29 | node [shape=Mrecord,style=filled] 30 | 31 | s [fillcolor=6,label="{string|s|len=3}"] 32 | } 33 | 34 | cjson -> json:j 35 | ctop -> stack:t 36 | json -> desc [style=invis] 37 | stack -> s [style=invis] 38 | s:s -> abc:h 39 | } -------------------------------------------------------------------------------- /tutorial05/images/parse_array04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/json-tutorial/645d80e849137c85e5a9e54982a9fb50a462e5ea/tutorial05/images/parse_array04.png -------------------------------------------------------------------------------- /tutorial05/images/parse_array05.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=TB 3 | compound=true 4 | fontname="Inconsolata, Consolas" 5 | fontsize=10 6 | margin="0,0" 7 | ranksep=0.3 8 | nodesep=1 9 | penwidth=0.5 10 | 11 | node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, colorscheme=spectral7] 12 | edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] 13 | 14 | { 15 | node [shape=record, style=filled, margin=0.1, height=0.3] 16 | json [fillcolor=3, label="[|\"|a|b|c|\"|,|[|1|,|2|]|,|3|]|\\0"] 17 | stack [fillcolor=4, label="{string|s|len=3}| | | | | |"] 18 | abc [fillcolor=3, label="a|b|c|\\0"] 19 | } 20 | { 21 | node [shape=plaintext, margin=0] 22 | 23 | cjson [label="c->json"] 24 | ctop [label="c->top"] 25 | desc [style=solid,label="\l1. lept_parse()\l 2. lept_parse_value()\l 3. lept_parse_array()"] 26 | } 27 | 28 | cjson -> json:j 29 | ctop -> stack:t 30 | json -> desc [style=invis] 31 | stack:s -> abc:h 32 | } -------------------------------------------------------------------------------- /tutorial05/images/parse_array05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/json-tutorial/645d80e849137c85e5a9e54982a9fb50a462e5ea/tutorial05/images/parse_array05.png -------------------------------------------------------------------------------- /tutorial05/images/parse_array06.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=TB 3 | compound=true 4 | fontname="Inconsolata, Consolas" 5 | fontsize=10 6 | margin="0,0" 7 | ranksep=0.3 8 | nodesep=1 9 | penwidth=0.5 10 | 11 | node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, colorscheme=spectral7] 12 | edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] 13 | 14 | { 15 | node [shape=record, style=filled, margin=0.1, height=0.3] 16 | json [fillcolor=3, label="[|\"|a|b|c|\"|,|[|1|,|2|]|,|3|]|\\0"] 17 | stack [fillcolor=4, label="{string|s|len=3}| | | | | |"] 18 | abc [fillcolor=3, label="a|b|c|\\0"] 19 | } 20 | { 21 | node [shape=plaintext, margin=0] 22 | 23 | cjson [label="c->json"] 24 | ctop [label="c->top"] 25 | desc [style=solid,label="\l1. lept_parse()\l 2. lept_parse_value()\l 3. lept_parse_array()\l 4. lept_parse_value()\l 5. lept_parse_array()\l 6. lept_parse_value()\l 7. lept_parse_number()"] 26 | } 27 | 28 | { 29 | node [shape=Mrecord,style=filled] 30 | 31 | n1 [fillcolor=7,label="{number|n=1}"] 32 | } 33 | 34 | cjson -> json:j 35 | ctop -> stack:t 36 | json -> desc [style=invis] 37 | stack:s -> abc:h 38 | stack -> n1 [style=invis] 39 | } -------------------------------------------------------------------------------- /tutorial05/images/parse_array06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/json-tutorial/645d80e849137c85e5a9e54982a9fb50a462e5ea/tutorial05/images/parse_array06.png -------------------------------------------------------------------------------- /tutorial05/images/parse_array07.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=TB 3 | compound=true 4 | fontname="Inconsolata, Consolas" 5 | fontsize=10 6 | margin="0,0" 7 | ranksep=0.3 8 | nodesep=1 9 | penwidth=0.5 10 | 11 | node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, colorscheme=spectral7] 12 | edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] 13 | 14 | { 15 | node [shape=record, style=filled, margin=0.1, height=0.3] 16 | json [fillcolor=3, label="[|\"|a|b|c|\"|,|[|1|,|2|]|,|3|]|\\0"] 17 | stack [fillcolor=4, label="{string|s|len=3}|{number|n=1}| | | | |"] 18 | abc [fillcolor=3, label="a|b|c|\\0"] 19 | } 20 | { 21 | node [shape=plaintext, margin=0] 22 | 23 | cjson [label="c->json"] 24 | ctop [label="c->top"] 25 | desc [style=solid,label="\l1. lept_parse()\l 2. lept_parse_value()\l 3. lept_parse_array()\l 4. lept_parse_value()\l 5. lept_parse_array()"] 26 | } 27 | 28 | cjson -> json:j 29 | ctop -> stack:t 30 | json -> desc [style=invis] 31 | stack:s -> abc:h 32 | } -------------------------------------------------------------------------------- /tutorial05/images/parse_array07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/json-tutorial/645d80e849137c85e5a9e54982a9fb50a462e5ea/tutorial05/images/parse_array07.png -------------------------------------------------------------------------------- /tutorial05/images/parse_array08.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=TB 3 | compound=true 4 | fontname="Inconsolata, Consolas" 5 | fontsize=10 6 | margin="0,0" 7 | ranksep=0.3 8 | nodesep=1 9 | penwidth=0.5 10 | 11 | node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, colorscheme=spectral7] 12 | edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] 13 | 14 | { 15 | node [shape=record, style=filled, margin=0.1, height=0.3] 16 | json [fillcolor=3, label="[|\"|a|b|c|\"|,|[|1|,|2|]|,|3|]|\\0"] 17 | stack [fillcolor=4, label="{string|s|len=3}| | | | | |"] 18 | abc [fillcolor=3, label="a|b|c|\\0"] 19 | } 20 | { 21 | node [shape=plaintext, margin=0] 22 | 23 | cjson [label="c->json"] 24 | ctop [label="c->top"] 25 | desc [style=solid,label="\l1. lept_parse()\l 2. lept_parse_value()\l 3. lept_parse_array()\l"] 26 | } 27 | 28 | { 29 | node [shape=Mrecord,style=filled] 30 | 31 | a2 [fillcolor=2,label="{array|e|size=2}"] 32 | n1 [fillcolor=7,label="{number|n=1}"] 33 | n2 [fillcolor=7,label="{number|n=2}"] 34 | } 35 | 36 | cjson -> json:j 37 | ctop -> stack:t 38 | json -> desc [style=invis] 39 | stack:s -> abc:h 40 | a2:e -> n1; 41 | a2 -> n2 [style=invis] 42 | n1 -> n2 [style=dashed,constraint=false] 43 | stack -> a2 [style=invis] 44 | } -------------------------------------------------------------------------------- /tutorial05/images/parse_array08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/json-tutorial/645d80e849137c85e5a9e54982a9fb50a462e5ea/tutorial05/images/parse_array08.png -------------------------------------------------------------------------------- /tutorial05/images/parse_array09.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=TB 3 | compound=true 4 | fontname="Inconsolata, Consolas" 5 | fontsize=10 6 | margin="0,0" 7 | ranksep=0.3 8 | nodesep=1 9 | penwidth=0.5 10 | 11 | node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, colorscheme=spectral7] 12 | edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] 13 | 14 | { 15 | node [shape=record, style=filled, margin=0.1, height=0.3] 16 | json [fillcolor=3, label="[|\"|a|b|c|\"|,|[|1|,|2|]|,|3|]|\\0"] 17 | stack [fillcolor=4, label="{string|s|len=3}|{array|e|size=2}| | | | |"] 18 | abc [fillcolor=3, label="a|b|c|\\0"] 19 | } 20 | { 21 | node [shape=plaintext, margin=0] 22 | 23 | cjson [label="c->json"] 24 | ctop [label="c->top"] 25 | desc [style=solid,label="\l1. lept_parse()\l 2. lept_parse_value()\l 3. lept_parse_array()\l"] 26 | } 27 | 28 | { 29 | node [shape=Mrecord,style=filled] 30 | 31 | n1 [fillcolor=7,label="{number|n=1}"] 32 | n2 [fillcolor=7,label="{number|n=2}"] 33 | } 34 | 35 | cjson -> json:j 36 | ctop -> stack:t 37 | json -> desc [style=invis] 38 | stack:s -> abc:h 39 | stack:e -> n1; 40 | stack:e -> n2 [style=invis] 41 | n1 -> n2 [style=dashed,constraint=false] 42 | } -------------------------------------------------------------------------------- /tutorial05/images/parse_array09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/json-tutorial/645d80e849137c85e5a9e54982a9fb50a462e5ea/tutorial05/images/parse_array09.png -------------------------------------------------------------------------------- /tutorial05/images/parse_array10.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=TB 3 | compound=true 4 | fontname="Inconsolata, Consolas" 5 | fontsize=10 6 | margin="0,0" 7 | ranksep=0.3 8 | nodesep=1 9 | penwidth=0.5 10 | 11 | node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, colorscheme=spectral7] 12 | edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] 13 | 14 | { 15 | node [shape=record, style=filled, margin=0.1, height=0.3] 16 | json [fillcolor=3, label="[|\"|a|b|c|\"|,|[|1|,|2|]|,|3|]|\\0"] 17 | stack [fillcolor=4, label=" | | | | | |"] 18 | abc [fillcolor=3, label="a|b|c|\\0"] 19 | } 20 | { 21 | node [shape=plaintext, margin=0] 22 | 23 | cjson [label="c->json"] 24 | ctop [label="c->top"] 25 | desc [style=solid,label="\l1. lept_parse()\l 2. lept_parse_value()\l 3. lept_parse_array()\l"] 26 | } 27 | 28 | { 29 | node [shape=Mrecord,style=filled] 30 | 31 | a1 [fillcolor=2,label="{array|e|size=3}"] 32 | s [fillcolor=6,label="{string|s|len=3}"] 33 | a2 [fillcolor=2,label="{array|e|size=2}"] 34 | n1 [fillcolor=7,label="{number|n=1}"] 35 | n2 [fillcolor=7,label="{number|n=2}"] 36 | n3 [fillcolor=7,label="{number|n=3}"] 37 | } 38 | 39 | cjson -> json:j 40 | ctop -> stack:t 41 | json -> desc [style=invis] 42 | stack -> a1 [style=invis] 43 | a1:e -> s 44 | s:s -> abc:h 45 | a2:e -> n1; 46 | a1 -> { a2; n3 } [style=invis] 47 | a2:e -> n2 [style=invis] 48 | n1 -> n2 [style=dashed,constraint=false] 49 | s -> a2 -> n3 [style=dashed,constraint=false] 50 | } -------------------------------------------------------------------------------- /tutorial05/images/parse_array10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/json-tutorial/645d80e849137c85e5a9e54982a9fb50a462e5ea/tutorial05/images/parse_array10.png -------------------------------------------------------------------------------- /tutorial05/leptjson.h: -------------------------------------------------------------------------------- 1 | #ifndef LEPTJSON_H__ 2 | #define LEPTJSON_H__ 3 | 4 | #include /* size_t */ 5 | 6 | typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; 7 | 8 | typedef struct lept_value lept_value; 9 | 10 | struct lept_value { 11 | union { 12 | struct { lept_value* e; size_t size; }a; /* array: elements, element count */ 13 | struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ 14 | double n; /* number */ 15 | }u; 16 | lept_type type; 17 | }; 18 | 19 | enum { 20 | LEPT_PARSE_OK = 0, 21 | LEPT_PARSE_EXPECT_VALUE, 22 | LEPT_PARSE_INVALID_VALUE, 23 | LEPT_PARSE_ROOT_NOT_SINGULAR, 24 | LEPT_PARSE_NUMBER_TOO_BIG, 25 | LEPT_PARSE_MISS_QUOTATION_MARK, 26 | LEPT_PARSE_INVALID_STRING_ESCAPE, 27 | LEPT_PARSE_INVALID_STRING_CHAR, 28 | LEPT_PARSE_INVALID_UNICODE_HEX, 29 | LEPT_PARSE_INVALID_UNICODE_SURROGATE, 30 | LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET 31 | }; 32 | 33 | #define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) 34 | 35 | int lept_parse(lept_value* v, const char* json); 36 | 37 | void lept_free(lept_value* v); 38 | 39 | lept_type lept_get_type(const lept_value* v); 40 | 41 | #define lept_set_null(v) lept_free(v) 42 | 43 | int lept_get_boolean(const lept_value* v); 44 | void lept_set_boolean(lept_value* v, int b); 45 | 46 | double lept_get_number(const lept_value* v); 47 | void lept_set_number(lept_value* v, double n); 48 | 49 | const char* lept_get_string(const lept_value* v); 50 | size_t lept_get_string_length(const lept_value* v); 51 | void lept_set_string(lept_value* v, const char* s, size_t len); 52 | 53 | size_t lept_get_array_size(const lept_value* v); 54 | lept_value* lept_get_array_element(const lept_value* v, size_t index); 55 | 56 | #endif /* LEPTJSON_H__ */ 57 | -------------------------------------------------------------------------------- /tutorial05/tutorial05.md: -------------------------------------------------------------------------------- 1 | # 从零开始的 JSON 库教程(五):解析数组 2 | 3 | * Milo Yip 4 | * 2016/10/7 5 | 6 | 本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第五个单元。代码位于 [json-tutorial/tutorial05](https://github.com/miloyip/json-tutorial/blob/master/tutorial05)。 7 | 8 | 本单元内容: 9 | 10 | 1. [JSON 数组](#1-json-数组) 11 | 2. [数据结构](#2-数据结构) 12 | 3. [解析过程](#3-解析过程) 13 | 4. [实现](#4-实现) 14 | 5. [总结与练习](#5-总结与练习) 15 | 16 | ## 1. JSON 数组 17 | 18 | 从零到这第五单元,我们终于要解析一个 JSON 的复合数据类型了。一个 JSON 数组可以包含零至多个元素,而这些元素也可以是数组类型。换句话说,我们可以表示嵌套(nested)的数据结构。先来看看 JSON 数组的语法: 19 | 20 | ~~~ 21 | array = %x5B ws [ value *( ws %x2C ws value ) ] ws %x5D 22 | ~~~ 23 | 24 | 当中,`%x5B` 是左中括号 `[`,`%x2C` 是逗号 `,`,`%x5D` 是右中括号 `]` ,`ws` 是空白字符。一个数组可以包含零至多个值,以逗号分隔,例如 `[]`、`[1,2,true]`、`[[1,2],[3,4],"abc"]` 都是合法的数组。但注意 JSON 不接受末端额外的逗号,例如 `[1,2,]` 是不合法的(许多编程语言如 C/C++、Javascript、Java、C# 都容许数组初始值包含末端逗号)。 25 | 26 | JSON 数组的语法很简单,实现的难点不在语法上,而是怎样管理内存。 27 | 28 | ## 2. 数据结构 29 | 30 | 首先,我们需要设计存储 JSON 数组类型的数据结构。 31 | 32 | JSON 数组存储零至多个元素,最简单就是使用 C 语言的数组。数组最大的好处是能以 $O(1)$ 用索引访问任意元素,次要好处是内存布局紧凑,省内存之余还有高缓存一致性(cache coherence)。但数组的缺点是不能快速插入元素,而且我们在解析 JSON 数组的时候,还不知道应该分配多大的数组才合适。 33 | 34 | 另一个选择是链表(linked list),它的最大优点是可快速地插入元素(开端、末端或中间),但需要以 $O(n)$ 时间去经索引取得内容。如果我们只需顺序遍历,那么是没有问题的。还有一个小缺点,就是相对数组而言,链表在存储每个元素时有额外内存开销(存储下一节点的指针),而且遍历时元素所在的内存可能不连续,令缓存不命中(cache miss)的机会上升。 35 | 36 | 我见过一些 JSON 库选择了链表,而这里则选择了数组。我们将会通过之前在解析字符串时实现的堆栈,来解决解析 JSON 数组时未知数组大小的问题。 37 | 38 | 决定之后,我们在 `lept_value` 的 `union` 中加入数组的结构: 39 | 40 | ~~~c 41 | typedef struct lept_value lept_value; 42 | 43 | struct lept_value { 44 | union { 45 | struct { lept_value* e; size_t size; }a; /* array */ 46 | struct { char* s; size_t len; }s; 47 | double n; 48 | }u; 49 | lept_type type; 50 | }; 51 | ~~~ 52 | 53 | 由于 `lept_value` 内使用了自身类型的指针,我们必须前向声明(forward declare)此类型。 54 | 55 | 另外,注意这里 `size` 是元素的个数,不是字节单位。我们增加两个 API 去访问 JSON 数组类型的值: 56 | 57 | ~~~c 58 | size_t lept_get_array_size(const lept_value* v) { 59 | assert(v != NULL && v->type == LEPT_ARRAY); 60 | return v->u.a.size; 61 | } 62 | 63 | lept_value* lept_get_array_element(const lept_value* v, size_t index) { 64 | assert(v != NULL && v->type == LEPT_ARRAY); 65 | assert(index < v->u.a.size); 66 | return &v->u.a.e[index]; 67 | } 68 | ~~~ 69 | 70 | 暂时我们不考虑增删数组元素,这些功能留待第八单元讨论。 71 | 72 | 然后,我们写一个单元测试去试用这些 API(练习需要更多测试)。 73 | 74 | ~~~c 75 | #if defined(_MSC_VER) 76 | #define EXPECT_EQ_SIZE_T(expect, actual) EXPECT_EQ_BASE((expect) == (actual), (size_t)expect, (size_t)actual, "%Iu") 77 | #else 78 | #define EXPECT_EQ_SIZE_T(expect, actual) EXPECT_EQ_BASE((expect) == (actual), (size_t)expect, (size_t)actual, "%zu") 79 | #endif 80 | 81 | static void test_parse_array() { 82 | lept_value v; 83 | 84 | lept_init(&v); 85 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ ]")); 86 | EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); 87 | EXPECT_EQ_SIZE_T(0, lept_get_array_size(&v)); 88 | lept_free(&v); 89 | } 90 | ~~~ 91 | 92 | 在之前的单元中,作者已多次重申,C 语言的数组大小应该使用 `size_t` 类型。因为我们要验证 `lept_get_array_size()` 返回值是否正确,所以再为单元测试框架添加一个宏 `EXPECT_EQ_SIZE_T`。麻烦之处在于,ANSI C(C89)并没有的 `size_t` 打印方法,在 C99 则加入了 `"%zu"`,但 VS2015 中才有,之前的 VC 版本使用非标准的 `"%Iu"`。因此,上面的代码使用条件编译去区分 VC 和其他编译器。虽然这部分不跨平台也不是 ANSI C 标准,但它只在测试程序中,不太影响程序库的跨平台性。 93 | 94 | ## 3. 解析过程 95 | 96 | 我们在解析 JSON 字符串时,因为在开始时不能知道字符串的长度,而又需要进行转义,所以需要一个临时缓冲区去存储解析后的结果。我们为此实现了一个动态增长的堆栈,可以不断压入字符,最后一次性把整个字符串弹出,复制至新分配的内存之中。 97 | 98 | 对于 JSON 数组,我们也可以用相同的方法,而且,我们可以用同一个堆栈!我们只需要把每个解析好的元素压入堆栈,解析到数组结束时,再一次性把所有元素弹出,复制至新分配的内存之中。 99 | 100 | 但和字符串有点不一样,如果把 JSON 当作一棵树的数据结构,JSON 字符串是叶节点,而 JSON 数组是中间节点。在叶节点的解析函数中,我们怎样使用那个堆栈也可以,只要最后还原就好了。但对于数组这样的中间节点,共用这个堆栈没问题么? 101 | 102 | 答案是:只要在解析函数结束时还原堆栈的状态,就没有问题。为了直观地了解这个解析过程,我们用连环图去展示 `["abc",[1,2],3]` 的解析过程。 103 | 104 | 首先,我们遇到 `[`,进入 `lept_parse_array()`: 105 | 106 | ![ ](images/parse_array01.png) 107 | 108 | 生成一个临时的 `lept_value`,用于存储之后的元素。我们再调用 `lept_parse_value()` 去解析这个元素值,因为遇到 `"` 进入 `lept_parse_string()`: 109 | 110 | ![ ](images/parse_array02.png) 111 | 112 | 在 `lept_parse_string()` 中,不断解析字符直至遇到 `"`,过程中把每个字符压栈: 113 | 114 | ![ ](images/parse_array03.png) 115 | 116 | 最后在 `lept_parse_string()` 中,把栈上 3 个字符弹出,分配内存,生成字符串值: 117 | 118 | ![ ](images/parse_array04.png) 119 | 120 | 返回上一层 `lept_parse_array()`,把临时元素压栈: 121 | 122 | ![ ](images/parse_array05.png) 123 | 124 | 然后我们再遇到 `[`,进入另一个 `lept_parse_array()`。它发现第一个元素是数字类型,所认调用 `lept_parse_number()`,生成一个临时的元素值: 125 | 126 | ![ ](images/parse_array06.png) 127 | 128 | 之后把该临时的元素值压栈: 129 | 130 | ![ ](images/parse_array07.png) 131 | 132 | 接着再解析第二个元素。我们遇到了 `]`,从栈上弹出 2 个元素,分配内存,生成数组(虚线代表是连续的内存): 133 | 134 | ![ ](images/parse_array08.png) 135 | 136 | 那个数组是上层数组的元素,我们把它压栈。现时栈内已有两个元素,我们再继续解析下一个元素: 137 | 138 | ![ ](images/parse_array09.png) 139 | 140 | 最后,遇到了 `]`,可以弹出栈内 3 个元素,分配内存,生成数组: 141 | 142 | ![ ](images/parse_array10.png) 143 | 144 | ## 4. 实现 145 | 146 | 经过这个详细的图解,实现 `lept_parse_array()` 应该没有难度。以下是半制成品: 147 | 148 | ~~~c 149 | static int lept_parse_value(lept_context* c, lept_value* v); /* 前向声明 */ 150 | 151 | static int lept_parse_array(lept_context* c, lept_value* v) { 152 | size_t size = 0; 153 | int ret; 154 | EXPECT(c, '['); 155 | if (*c->json == ']') { 156 | c->json++; 157 | v->type = LEPT_ARRAY; 158 | v->u.a.size = 0; 159 | v->u.a.e = NULL; 160 | return LEPT_PARSE_OK; 161 | } 162 | for (;;) { 163 | lept_value e; 164 | lept_init(&e); 165 | if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) 166 | return ret; 167 | memcpy(lept_context_push(c, sizeof(lept_value)), &e, sizeof(lept_value)); 168 | size++; 169 | if (*c->json == ',') 170 | c->json++; 171 | else if (*c->json == ']') { 172 | c->json++; 173 | v->type = LEPT_ARRAY; 174 | v->u.a.size = size; 175 | size *= sizeof(lept_value); 176 | memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c, size), size); 177 | return LEPT_PARSE_OK; 178 | } 179 | else 180 | return LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET; 181 | } 182 | } 183 | 184 | static int lept_parse_value(lept_context* c, lept_value* v) { 185 | switch (*c->json) { 186 | /* ... */ 187 | case '[': return lept_parse_array(c, v); 188 | } 189 | } 190 | ~~~ 191 | 192 | 简单说明的话,就是在循环中建立一个临时值(`lept_value e`),然后调用 `lept_parse_value()` 去把元素解析至这个临时值,完成后把临时值压栈。当遇到 `]`,把栈内的元素弹出,分配内存,生成数组值。 193 | 194 | 注意到,`lept_parse_value()` 会调用 `lept_parse_array()`,而 `lept_parse_array()` 又会调用 `lept_parse_value()`,这是互相引用,所以必须要加入函数前向声明。 195 | 196 | 最后,我想告诉同学,实现这个函数时,我曾经制造一个不明显的 bug。这个函数有两个 `memcpy()`,第一个「似乎」是可以避免的,先压栈取得元素的指针,给 `lept_parse_value`: 197 | 198 | ~~~c 199 | for (;;) { 200 | /* bug! */ 201 | lept_value* e = lept_context_push(c, sizeof(lept_value)); 202 | lept_init(e); 203 | size++; 204 | if ((ret = lept_parse_value(c, e)) != LEPT_PARSE_OK) 205 | return ret; 206 | /* ... */ 207 | } 208 | ~~~ 209 | 210 | 这种写法为什么会有 bug?这是第 5 条练习题。 211 | 212 | ## 5. 总结与练习 213 | 214 | 1. 编写 `test_parse_array()` 单元测试,解析以下 2 个 JSON。由于数组是复合的类型,不能使用一个宏去测试结果,请使用各个 API 检查解析后的内容。 215 | 216 | ~~~js 217 | [ null , false , true , 123 , "abc" ] 218 | [ [ ] , [ 0 ] , [ 0 , 1 ] , [ 0 , 1 , 2 ] ] 219 | ~~~ 220 | 221 | 2. 现时的测试结果应该是失败的,因为 `lept_parse_array()` 里没有处理空白字符,加进合适的 `lept_parse_whitespace()` 令测试通过。 222 | 223 | 3. 使用[第三单元解答篇](../tutorial03_answer/tutorial03_answer.md)介绍的检测内存泄漏工具,会发现测试中有内存泄漏。很明显在 `lept_parse_array()` 中使用到 `malloc()` 分配内存,但却没有对应的 `free()`。应该在哪里释放内存?修改代码,使工具不再检测到相关的内存泄漏。 224 | 225 | 4. 开启 test.c 中两处被 `#if 0 ... #endif` 关闭的测试,本来 `lept_parse_array()` 已经能处理这些测试。然而,运行时会发现 `Assertion failed: (c.top == 0)` 断言失败。这是由于,当错误发生时,仍然有一些临时值在堆栈里,既没有放进数组,也没有被释放。修改 `lept_parse_array()`,当遇到错误时,从堆栈中弹出并释放那些临时值,然后才返回错误码。 226 | 227 | 5. 第 4 节那段代码为什么会有 bug? 228 | 229 | 如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 230 | -------------------------------------------------------------------------------- /tutorial05_answer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.6) 2 | project (leptjson_test C) 3 | 4 | if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") 6 | endif() 7 | 8 | add_library(leptjson leptjson.c) 9 | add_executable(leptjson_test test.c) 10 | target_link_libraries(leptjson_test leptjson) 11 | -------------------------------------------------------------------------------- /tutorial05_answer/leptjson.h: -------------------------------------------------------------------------------- 1 | #ifndef LEPTJSON_H__ 2 | #define LEPTJSON_H__ 3 | 4 | #include /* size_t */ 5 | 6 | typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; 7 | 8 | typedef struct lept_value lept_value; 9 | 10 | struct lept_value { 11 | union { 12 | struct { lept_value* e; size_t size; }a; /* array: elements, element count */ 13 | struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ 14 | double n; /* number */ 15 | }u; 16 | lept_type type; 17 | }; 18 | 19 | enum { 20 | LEPT_PARSE_OK = 0, 21 | LEPT_PARSE_EXPECT_VALUE, 22 | LEPT_PARSE_INVALID_VALUE, 23 | LEPT_PARSE_ROOT_NOT_SINGULAR, 24 | LEPT_PARSE_NUMBER_TOO_BIG, 25 | LEPT_PARSE_MISS_QUOTATION_MARK, 26 | LEPT_PARSE_INVALID_STRING_ESCAPE, 27 | LEPT_PARSE_INVALID_STRING_CHAR, 28 | LEPT_PARSE_INVALID_UNICODE_HEX, 29 | LEPT_PARSE_INVALID_UNICODE_SURROGATE, 30 | LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET 31 | }; 32 | 33 | #define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) 34 | 35 | int lept_parse(lept_value* v, const char* json); 36 | 37 | void lept_free(lept_value* v); 38 | 39 | lept_type lept_get_type(const lept_value* v); 40 | 41 | #define lept_set_null(v) lept_free(v) 42 | 43 | int lept_get_boolean(const lept_value* v); 44 | void lept_set_boolean(lept_value* v, int b); 45 | 46 | double lept_get_number(const lept_value* v); 47 | void lept_set_number(lept_value* v, double n); 48 | 49 | const char* lept_get_string(const lept_value* v); 50 | size_t lept_get_string_length(const lept_value* v); 51 | void lept_set_string(lept_value* v, const char* s, size_t len); 52 | 53 | size_t lept_get_array_size(const lept_value* v); 54 | lept_value* lept_get_array_element(const lept_value* v, size_t index); 55 | 56 | #endif /* LEPTJSON_H__ */ 57 | -------------------------------------------------------------------------------- /tutorial05_answer/tutorial05_answer.md: -------------------------------------------------------------------------------- 1 | # 从零开始的 JSON 库教程(五):解析数组解答篇 2 | 3 | * Milo Yip 4 | * 2016/10/13 5 | 6 | 本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第五个单元解答篇。解答代码位于 [json-tutorial/tutorial05_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial05_answer)。 7 | 8 | ## 1. 编写 `test_parse_array()` 单元测试 9 | 10 | 这个练习纯粹为了熟习数组的访问 API。新增的第一个 JSON 只需平凡的检测。第二个 JSON 有特定模式,第 i 个子数组的长度为 i,每个子数组的第 j 个元素是数字值 j,所以可用两层 for 循环测试。 11 | 12 | ~~~c 13 | static void test_parse_array() { 14 | size_t i, j; 15 | lept_value v; 16 | 17 | /* ... */ 18 | 19 | lept_init(&v); 20 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ null , false , true , 123 , \"abc\" ]")); 21 | EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); 22 | EXPECT_EQ_SIZE_T(5, lept_get_array_size(&v)); 23 | EXPECT_EQ_INT(LEPT_NULL, lept_get_type(lept_get_array_element(&v, 0))); 24 | EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(lept_get_array_element(&v, 1))); 25 | EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(lept_get_array_element(&v, 2))); 26 | EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_array_element(&v, 3))); 27 | EXPECT_EQ_INT(LEPT_STRING, lept_get_type(lept_get_array_element(&v, 4))); 28 | EXPECT_EQ_DOUBLE(123.0, lept_get_number(lept_get_array_element(&v, 3))); 29 | EXPECT_EQ_STRING("abc", lept_get_string(lept_get_array_element(&v, 4)), lept_get_string_length(lept_get_array_element(&v, 4))); 30 | lept_free(&v); 31 | 32 | lept_init(&v); 33 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ [ ] , [ 0 ] , [ 0 , 1 ] , [ 0 , 1 , 2 ] ]")); 34 | EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); 35 | EXPECT_EQ_SIZE_T(4, lept_get_array_size(&v)); 36 | for (i = 0; i < 4; i++) { 37 | lept_value* a = lept_get_array_element(&v, i); 38 | EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(a)); 39 | EXPECT_EQ_SIZE_T(i, lept_get_array_size(a)); 40 | for (j = 0; j < i; j++) { 41 | lept_value* e = lept_get_array_element(a, j); 42 | EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(e)); 43 | EXPECT_EQ_DOUBLE((double)j, lept_get_number(e)); 44 | } 45 | } 46 | lept_free(&v); 47 | } 48 | ~~~ 49 | 50 | ## 2. 解析空白字符 51 | 52 | 按现时的 `lept_parse_array()` 的编写方式,需要加入 3 个 `lept_parse_whitespace()` 调用,分别是解析 `[` 之后,元素之后,以及 `,` 之后: 53 | 54 | ~~~c 55 | static int lept_parse_array(lept_context* c, lept_value* v) { 56 | /* ... */ 57 | EXPECT(c, '['); 58 | lept_parse_whitespace(c); 59 | /* ... */ 60 | for (;;) { 61 | /* ... */ 62 | if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) 63 | return ret; 64 | /* ... */ 65 | lept_parse_whitespace(c); 66 | if (*c->json == ',') { 67 | c->json++; 68 | lept_parse_whitespace(c); 69 | } 70 | /* ... */ 71 | } 72 | } 73 | ~~~ 74 | 75 | ## 3. 内存泄漏 76 | 77 | 成功测试那 3 个 JSON 后,使用内存泄漏检测工具会发现 `lept_parse_array()` 用 `malloc()`分配的内存没有被释放: 78 | 79 | ~~~ 80 | ==154== 124 (120 direct, 4 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 4 81 | ==154== at 0x4C28C20: malloc (vg_replace_malloc.c:296) 82 | ==154== by 0x409D82: lept_parse_array (in /json-tutorial/tutorial05/build/leptjson_test) 83 | ==154== by 0x409E91: lept_parse_value (in /json-tutorial/tutorial05/build/leptjson_test) 84 | ==154== by 0x409F14: lept_parse (in /json-tutorial/tutorial05/build/leptjson_test) 85 | ==154== by 0x405261: test_parse_array (in /json-tutorial/tutorial05/build/leptjson_test) 86 | ==154== by 0x408C72: test_parse (in /json-tutorial/tutorial05/build/leptjson_test) 87 | ==154== by 0x40916A: main (in /json-tutorial/tutorial05/build/leptjson_test) 88 | ==154== 89 | ==154== 240 (96 direct, 144 indirect) bytes in 1 blocks are definitely lost in loss record 4 of 4 90 | ==154== at 0x4C28C20: malloc (vg_replace_malloc.c:296) 91 | ==154== by 0x409D82: lept_parse_array (in /json-tutorial/tutorial05/build/leptjson_test) 92 | ==154== by 0x409E91: lept_parse_value (in /json-tutorial/tutorial05/build/leptjson_test) 93 | ==154== by 0x409F14: lept_parse (in /json-tutorial/tutorial05/build/leptjson_test) 94 | ==154== by 0x40582C: test_parse_array (in /json-tutorial/tutorial05/build/leptjson_test) 95 | ==154== by 0x408C72: test_parse (in /json-tutorial/tutorial05/build/leptjson_test) 96 | ==154== by 0x40916A: main (in /json-tutorial/tutorial05/build/leptjson_test) 97 | ~~~ 98 | 99 | 很明显,有 `malloc()` 就要有对应的 `free()`。正确的释放位置应该放置在 `lept_free()`,当值被释放时,该值拥有的内存也在那里释放。之前字符串的释放也是放在这里: 100 | 101 | ~~~c 102 | void lept_free(lept_value* v) { 103 | assert(v != NULL); 104 | if (v->type == LEPT_STRING) 105 | free(v->u.s.s); 106 | v->type = LEPT_NULL; 107 | } 108 | ~~~ 109 | 110 | 但对于数组,我们应该先把数组内的元素通过递归调用 `lept_free()` 释放,然后才释放本身的 `v->u.a.e`: 111 | 112 | ~~~c 113 | void lept_free(lept_value* v) { 114 | size_t i; 115 | assert(v != NULL); 116 | switch (v->type) { 117 | case LEPT_STRING: 118 | free(v->u.s.s); 119 | break; 120 | case LEPT_ARRAY: 121 | for (i = 0; i < v->u.a.size; i++) 122 | lept_free(&v->u.a.e[i]); 123 | free(v->u.a.e); 124 | break; 125 | default: break; 126 | } 127 | v->type = LEPT_NULL; 128 | } 129 | ~~~ 130 | 131 | 修改之后,再运行内存泄漏检测工具,确保问题已被修正。 132 | 133 | ## 4. 解析错误时的内存处理 134 | 135 | 遇到解析错误时,我们可能在之前已压入了一些值在自定义堆栈上。如果没有处理,最后会在 `lept_parse()` 中发现堆栈上还有一些值,做成断言失败。所以,遇到解析错误时,我们必须弹出并释放那些值。 136 | 137 | 在 `lept_parse_array` 中,原本遇到解析失败时,会直接返回错误码。我们把它改为 `break` 离开循环,在循环结束后的地方用 `lept_free()` 释放从堆栈弹出的值,然后才返回错误码: 138 | 139 | ~~~c 140 | static int lept_parse_array(lept_context* c, lept_value* v) { 141 | /* ... */ 142 | for (;;) { 143 | /* ... */ 144 | if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) 145 | break; 146 | /* ... */ 147 | if (*c->json == ',') { 148 | /* ... */ 149 | } 150 | else if (*c->json == ']') { 151 | /* ... */ 152 | } 153 | else { 154 | ret = LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET; 155 | break; 156 | } 157 | } 158 | /* Pop and free values on the stack */ 159 | for (i = 0; i < size; i++) 160 | lept_free((lept_value*)lept_context_pop(c, sizeof(lept_value))); 161 | return ret; 162 | } 163 | ~~~ 164 | 165 | ## 5. bug 的解释 166 | 167 | 这个 bug 源于压栈时,会获得一个指针 `e`,指向从堆栈分配到的空间: 168 | 169 | ~~~c 170 | for (;;) { 171 | /* bug! */ 172 | lept_value* e = lept_context_push(c, sizeof(lept_value)); 173 | lept_init(e); 174 | size++; 175 | if ((ret = lept_parse_value(c, e)) != LEPT_PARSE_OK) 176 | return ret; 177 | /* ... */ 178 | } 179 | ~~~ 180 | 181 | 然后,我们把这个指针调用 `lept_parse_value(c, e)`,这里会出现问题,因为 `lept_parse_value()` 及之下的函数都需要调用 `lept_context_push()`,而 `lept_context_push()` 在发现栈满了的时候会用 `realloc()` 扩容。这时候,我们上层的 `e` 就会失效,变成一个悬挂指针(dangling pointer),而且 `lept_parse_value(c, e)` 会通过这个指针写入解析结果,造成非法访问。 182 | 183 | 在使用 C++ 容器时,也会遇到类似的问题。从容器中取得的迭代器(iterator)后,如果改动容器内容,之前的迭代器会失效。这里的悬挂指针问题也是相同的。 184 | 185 | 但这种 bug 有时可能在简单测试中不能自动发现,因为问题只有堆栈满了才会出现。从测试的角度看,我们需要一些压力测试(stress test),测试更大更复杂的数据。但从编程的角度看,我们要谨慎考虑变量的生命周期,尽量从编程阶段避免出现问题。例如把 `lept_context_push()` 的 API 改为: 186 | 187 | ~~~c 188 | static void lept_context_push(lept_context* c, const void* data, size_t size); 189 | ~~~ 190 | 191 | 这样就确把数据压入栈内,避免了返回指针的生命周期问题。但我们之后会发现,原来的 API 设计在一些情况会更方便一些,例如在把字符串值转化(stringify)为 JSON 时,我们可以预先在堆栈分配字符串所需的最大空间,而当时是未有数据填充进去的。 192 | 193 | 无论如何,我们编程时都要考虑清楚变量的生命周期,特别是指针的生命周期。 194 | 195 | ## 6. 总结 196 | 197 | 经过对数组的解析,我们也了解到如何利用递归处理复合型的数据类型解析。与一些用链表或自动扩展的动态数组的实现比较,我们利用了自定义堆栈作为缓冲区,能分配最紧凑的数组作存储之用,会比其他实现更省内存。我们完成了数组类型后,只余下对象类型了。 198 | 199 | 如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 200 | -------------------------------------------------------------------------------- /tutorial06/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.6) 2 | project (leptjson_test C) 3 | 4 | if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") 6 | endif() 7 | 8 | add_library(leptjson leptjson.c) 9 | add_executable(leptjson_test test.c) 10 | target_link_libraries(leptjson_test leptjson) 11 | -------------------------------------------------------------------------------- /tutorial06/leptjson.h: -------------------------------------------------------------------------------- 1 | #ifndef LEPTJSON_H__ 2 | #define LEPTJSON_H__ 3 | 4 | #include /* size_t */ 5 | 6 | typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; 7 | 8 | typedef struct lept_value lept_value; 9 | typedef struct lept_member lept_member; 10 | 11 | struct lept_value { 12 | union { 13 | struct { lept_member* m; size_t size; }o; /* object: members, member count */ 14 | struct { lept_value* e; size_t size; }a; /* array: elements, element count */ 15 | struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ 16 | double n; /* number */ 17 | }u; 18 | lept_type type; 19 | }; 20 | 21 | struct lept_member { 22 | char* k; size_t klen; /* member key string, key string length */ 23 | lept_value v; /* member value */ 24 | }; 25 | 26 | enum { 27 | LEPT_PARSE_OK = 0, 28 | LEPT_PARSE_EXPECT_VALUE, 29 | LEPT_PARSE_INVALID_VALUE, 30 | LEPT_PARSE_ROOT_NOT_SINGULAR, 31 | LEPT_PARSE_NUMBER_TOO_BIG, 32 | LEPT_PARSE_MISS_QUOTATION_MARK, 33 | LEPT_PARSE_INVALID_STRING_ESCAPE, 34 | LEPT_PARSE_INVALID_STRING_CHAR, 35 | LEPT_PARSE_INVALID_UNICODE_HEX, 36 | LEPT_PARSE_INVALID_UNICODE_SURROGATE, 37 | LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, 38 | LEPT_PARSE_MISS_KEY, 39 | LEPT_PARSE_MISS_COLON, 40 | LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET 41 | }; 42 | 43 | #define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) 44 | 45 | int lept_parse(lept_value* v, const char* json); 46 | 47 | void lept_free(lept_value* v); 48 | 49 | lept_type lept_get_type(const lept_value* v); 50 | 51 | #define lept_set_null(v) lept_free(v) 52 | 53 | int lept_get_boolean(const lept_value* v); 54 | void lept_set_boolean(lept_value* v, int b); 55 | 56 | double lept_get_number(const lept_value* v); 57 | void lept_set_number(lept_value* v, double n); 58 | 59 | const char* lept_get_string(const lept_value* v); 60 | size_t lept_get_string_length(const lept_value* v); 61 | void lept_set_string(lept_value* v, const char* s, size_t len); 62 | 63 | size_t lept_get_array_size(const lept_value* v); 64 | lept_value* lept_get_array_element(const lept_value* v, size_t index); 65 | 66 | size_t lept_get_object_size(const lept_value* v); 67 | const char* lept_get_object_key(const lept_value* v, size_t index); 68 | size_t lept_get_object_key_length(const lept_value* v, size_t index); 69 | lept_value* lept_get_object_value(const lept_value* v, size_t index); 70 | 71 | #endif /* LEPTJSON_H__ */ 72 | -------------------------------------------------------------------------------- /tutorial06/tutorial06.md: -------------------------------------------------------------------------------- 1 | # 从零开始的 JSON 库教程(六):解析对象 2 | 3 | * Milo Yip 4 | * 2016/10/29 5 | 6 | 本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第六个单元。代码位于 [json-tutorial/tutorial06](https://github.com/miloyip/json-tutorial/blob/master/tutorial06)。 7 | 8 | 本单元内容: 9 | 10 | 1. [JSON 对象](#1-json-对象) 11 | 2. [数据结构](#2-数据结构) 12 | 3. [重构字符串解析](#3-重构字符串解析) 13 | 4. [实现](#4-实现) 14 | 5. [总结与练习](#5-总结与练习) 15 | 16 | ## 1. JSON 对象 17 | 18 | 此单元是本教程最后一个关于 JSON 解析器的部分。JSON 对象和 JSON 数组非常相似,区别包括 JSON 对象以花括号 `{}`(`U+007B`、`U+007D`)包裹表示,另外 JSON 对象由对象成员(member)组成,而 JSON 数组由 JSON 值组成。所谓对象成员,就是键值对,键必须为 JSON 字符串,然后值是任何 JSON 值,中间以冒号 `:`(`U+003A`)分隔。完整语法如下: 19 | 20 | ~~~ 21 | member = string ws %x3A ws value 22 | object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D 23 | ~~~ 24 | 25 | ## 2. 数据结构 26 | 27 | 要表示键值对的集合,有很多数据结构可供选择,例如: 28 | 29 | * 动态数组(dynamic array):可扩展容量的数组,如 C++ 的 [`std::vector`](https://en.cppreference.com/w/cpp/container/vector)。 30 | * 有序动态数组(sorted dynamic array):和动态数组相同,但保证元素已排序,可用二分搜寻查询成员。 31 | * 平衡树(balanced tree):平衡二叉树可有序地遍历成员,如红黑树和 C++ 的 [`std::map`](https://en.cppreference.com/w/cpp/container/map)([`std::multi_map`](https://en.cppreference.com/w/cpp/container/multimap) 支持重复键)。 32 | * 哈希表(hash table):通过哈希函数能实现平均 O(1) 查询,如 C++11 的 [`std::unordered_map`](https://en.cppreference.com/w/cpp/container/unordered_map)([`unordered_multimap`](https://en.cppreference.com/w/cpp/container/unordered_multimap) 支持重复键)。 33 | 34 | 设一个对象有 n 个成员,数据结构的容量是 m,n ⩽ m,那么一些常用操作的时间/空间复杂度如下: 35 | 36 | | |动态数组 |有序动态数组|平衡树 |哈希表 | 37 | |---------------|:-------:|:----------:|:--------:|:--------------------:| 38 | |有序 |否 |是 |是 |否 | 39 | |自定成员次序 |可 |否 |否 |否 | 40 | |初始化 n 个成员|O(n) |O(n log n) |O(n log n)|平均 O(n)、最坏 O(n^2)| 41 | |加入成员 |分摊 O(1)|O(n) |O(log n) |平均 O(1)、最坏 O(n) | 42 | |移除成员 |O(n) |O(n) |O(log n) |平均 O(1)、最坏 O(n) | 43 | |查询成员 |O(n) |O(log n) |O(log n) |平均 O(1)、最坏 O(n) | 44 | |遍历成员 |O(n) |O(n) |O(n) |O(m) | 45 | |检测对象相等 |O(n^2) |O(n) |O(n) |平均 O(n)、最坏 O(n^2)| 46 | |空间 |O(m) |O(m) |O(n) |O(m) | 47 | 48 | 在 ECMA-404 标准中,并没有规定对象中每个成员的键一定要唯一的,也没有规定是否需要维持成员的次序。 49 | 50 | 为了简单起见,我们的 leptjson 选择用动态数组的方案。我们将在单元八才加入动态功能,所以这单元中,每个对象仅仅是成员的数组。那么它跟上一单元的数组非常接近: 51 | 52 | ~~~c 53 | typedef struct lept_value lept_value; 54 | typedef struct lept_member lept_member; 55 | 56 | struct lept_value { 57 | union { 58 | struct { lept_member* m; size_t size; }o; 59 | struct { lept_value* e; size_t size; }a; 60 | struct { char* s; size_t len; }s; 61 | double n; 62 | }u; 63 | lept_type type; 64 | }; 65 | 66 | struct lept_member { 67 | char* k; size_t klen; /* member key string, key string length */ 68 | lept_value v; /* member value */ 69 | }; 70 | ~~~ 71 | 72 | 成员结构 `lept_member` 是一个 `lept_value` 加上键的字符串。如同 JSON 字符串的值,我们也需要同时保留字符串的长度,因为字符串本身可能包含空字符 `\u0000`。 73 | 74 | 在这单元中,我们仅添加了最基本的访问函数,用于撰写单元测试: 75 | 76 | ~~~c 77 | size_t lept_get_object_size(const lept_value* v); 78 | const char* lept_get_object_key(const lept_value* v, size_t index); 79 | size_t lept_get_object_key_length(const lept_value* v, size_t index); 80 | lept_value* lept_get_object_value(const lept_value* v, size_t index); 81 | ~~~ 82 | 83 | 在软件开发过程中,许多时候,选择合适的数据结构后已等于完成一半工作。没有完美的数据结构,所以最好考虑多一些应用的场合,看看时间/空间复杂度以至相关系数是否合适。 84 | 85 | 接下来,我们就可以着手实现。 86 | 87 | ## 3. 重构字符串解析 88 | 89 | 在软件工程中,[代码重构](https://zh.wikipedia.org/wiki/%E4%BB%A3%E7%A0%81%E9%87%8D%E6%9E%84)(code refactoring)是指在不改变软件外在行为时,修改代码以改进结构。代码重构十分依赖于单元测试,因为我们是通过单元测试去维护代码的正确性。有了足够的单元测试,我们可以放胆去重构,尝试并评估不同的改进方式,找到合乎心意而且能通过单元测试的改动,我们才提交它。 90 | 91 | 我们知道,成员的键也是一个 JSON 字符串,然而,我们不使用 `lept_value` 存储键,因为这样会浪费了当中 `type` 这个无用的字段。由于 `lept_parse_string()` 是直接地把解析的结果写进一个 `lept_value`,所以我们先用「提取方法(extract method,见下注)」的重构方式,把解析 JSON 字符串及写入 `lept_value` 分拆成两部分: 92 | 93 | ~~~c 94 | /* 解析 JSON 字符串,把结果写入 str 和 len */ 95 | /* str 指向 c->stack 中的元素,需要在 c->stack */ 96 | static int lept_parse_string_raw(lept_context* c, char** str, size_t* len) { 97 | /* \todo */ 98 | } 99 | 100 | static int lept_parse_string(lept_context* c, lept_value* v) { 101 | int ret; 102 | char* s; 103 | size_t len; 104 | if ((ret = lept_parse_string_raw(c, &s, &len)) == LEPT_PARSE_OK) 105 | lept_set_string(v, s, len); 106 | return ret; 107 | } 108 | ~~~ 109 | 110 | 这样的话,我们实现对象的解析时,就可以使用 `lept_parse_string_raw()` 来解析 JSON 字符串,然后把结果复制至 `lept_member` 的 `k` 和 `klen` 字段。 111 | 112 | 注:在 Fowler 的经典著作 [1] 中,把各种重构方式分门别类,每个方式都有详细的步骤说明。由于书中以 Java 为例子,所以方式的名称使用了 Java 的述语,例如方法(method)。在 C 语言中,「提取方法」其实应该称为「提取函数」。 113 | 114 | [1] Fowler, Martin. Refactoring: improving the design of existing code. Pearson Education India, 2009. 中译本:熊节译,《重构——改善既有代码的设计》,人民邮电出版社,2010年。 115 | 116 | ## 4. 实现 117 | 118 | 解析对象与解析数组非常相似,所以我留空了几段作为练习。在解析数组时,我们把当前的元素以 `lept_value` 压入栈中,而在这里,我们则是以 `lept_member` 压入: 119 | 120 | ~~~c 121 | static int lept_parse_object(lept_context* c, lept_value* v) { 122 | size_t size; 123 | lept_member m; 124 | int ret; 125 | EXPECT(c, '{'); 126 | lept_parse_whitespace(c); 127 | if (*c->json == '}') { 128 | c->json++; 129 | v->type = LEPT_OBJECT; 130 | v->u.o.m = 0; 131 | v->u.o.size = 0; 132 | return LEPT_PARSE_OK; 133 | } 134 | m.k = NULL; 135 | size = 0; 136 | for (;;) { 137 | lept_init(&m.v); 138 | /* \todo parse key to m.k, m.klen */ 139 | /* \todo parse ws colon ws */ 140 | /* parse value */ 141 | if ((ret = lept_parse_value(c, &m.v)) != LEPT_PARSE_OK) 142 | break; 143 | memcpy(lept_context_push(c, sizeof(lept_member)), &m, sizeof(lept_member)); 144 | size++; 145 | m.k = NULL; /* ownership is transferred to member on stack */ 146 | /* \todo parse ws [comma | right-curly-brace] ws */ 147 | } 148 | /* \todo Pop and free members on the stack */ 149 | return ret; 150 | } 151 | ~~~ 152 | 153 | 要注意的是,我们要为 `m.k` 分配内存去存储键的字符串,若在整个对象解析时发生错误,也要记得释放栈中的 `lept_member` 的 `k`。 154 | 155 | 我们为解析对象定义了几个新的错误码: 156 | 157 | ~~~c 158 | enum { 159 | /* ... */ 160 | LEPT_PARSE_MISS_KEY, 161 | LEPT_PARSE_MISS_COLON, 162 | LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET 163 | }; 164 | ~~~ 165 | 166 | 在此不再赘述它们的意义了,可从以下的单元测试看到例子: 167 | 168 | ~~~c 169 | static void test_parse_miss_key() { 170 | TEST_ERROR(LEPT_PARSE_MISS_KEY, "{:1,"); 171 | TEST_ERROR(LEPT_PARSE_MISS_KEY, "{1:1,"); 172 | TEST_ERROR(LEPT_PARSE_MISS_KEY, "{true:1,"); 173 | TEST_ERROR(LEPT_PARSE_MISS_KEY, "{false:1,"); 174 | TEST_ERROR(LEPT_PARSE_MISS_KEY, "{null:1,"); 175 | TEST_ERROR(LEPT_PARSE_MISS_KEY, "{[]:1,"); 176 | TEST_ERROR(LEPT_PARSE_MISS_KEY, "{{}:1,"); 177 | TEST_ERROR(LEPT_PARSE_MISS_KEY, "{\"a\":1,"); 178 | } 179 | 180 | static void test_parse_miss_colon() { 181 | TEST_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\"}"); 182 | TEST_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\",\"b\"}"); 183 | } 184 | 185 | static void test_parse_miss_comma_or_curly_bracket() { 186 | TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1"); 187 | TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1]"); 188 | TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1 \"b\""); 189 | TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":{}"); 190 | } 191 | ~~~ 192 | 193 | ## 5. 总结与练习 194 | 195 | 在本单元中,除了谈及 JSON 对象的语法、可选的数据结构、实现方式,我们也轻轻谈及了重构的概念。有赖于测试驱动开发(TDD),我们可以不断重塑软件的内部结构。 196 | 197 | 完成这次练习之后,恭喜你,你已经完整地实现了一个符合标准的 JSON 解析器了。之后我们会完成更简单的生成器及其他访问功能。 198 | 199 | 由于对象和数组的相似性,此单元留空了较多实现部分作为练习: 200 | 201 | 1. 依第 3 节所述,重构 `lept_parse_string()`。重构前运行单元测试,重构后确保单元测试仍保持通过。 202 | 2. 打开 `test.c` 中两个 `#if 0`,运行单元测试,证实单元测试不通过。然后实现 `lept_parse_object()` 中的 `\todo` 部分。验证实现能通过单元测试。 203 | 3. 使用工具检测内存泄漏,解决它们。 204 | 205 | 如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 206 | -------------------------------------------------------------------------------- /tutorial06_answer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.6) 2 | project (leptjson_test C) 3 | 4 | if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") 6 | endif() 7 | 8 | add_library(leptjson leptjson.c) 9 | add_executable(leptjson_test test.c) 10 | target_link_libraries(leptjson_test leptjson) 11 | -------------------------------------------------------------------------------- /tutorial06_answer/leptjson.h: -------------------------------------------------------------------------------- 1 | #ifndef LEPTJSON_H__ 2 | #define LEPTJSON_H__ 3 | 4 | #include /* size_t */ 5 | 6 | typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; 7 | 8 | typedef struct lept_value lept_value; 9 | typedef struct lept_member lept_member; 10 | 11 | struct lept_value { 12 | union { 13 | struct { lept_member* m; size_t size; }o; /* object: members, member count */ 14 | struct { lept_value* e; size_t size; }a; /* array: elements, element count */ 15 | struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ 16 | double n; /* number */ 17 | }u; 18 | lept_type type; 19 | }; 20 | 21 | struct lept_member { 22 | char* k; size_t klen; /* member key string, key string length */ 23 | lept_value v; /* member value */ 24 | }; 25 | 26 | enum { 27 | LEPT_PARSE_OK = 0, 28 | LEPT_PARSE_EXPECT_VALUE, 29 | LEPT_PARSE_INVALID_VALUE, 30 | LEPT_PARSE_ROOT_NOT_SINGULAR, 31 | LEPT_PARSE_NUMBER_TOO_BIG, 32 | LEPT_PARSE_MISS_QUOTATION_MARK, 33 | LEPT_PARSE_INVALID_STRING_ESCAPE, 34 | LEPT_PARSE_INVALID_STRING_CHAR, 35 | LEPT_PARSE_INVALID_UNICODE_HEX, 36 | LEPT_PARSE_INVALID_UNICODE_SURROGATE, 37 | LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, 38 | LEPT_PARSE_MISS_KEY, 39 | LEPT_PARSE_MISS_COLON, 40 | LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET 41 | }; 42 | 43 | #define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) 44 | 45 | int lept_parse(lept_value* v, const char* json); 46 | 47 | void lept_free(lept_value* v); 48 | 49 | lept_type lept_get_type(const lept_value* v); 50 | 51 | #define lept_set_null(v) lept_free(v) 52 | 53 | int lept_get_boolean(const lept_value* v); 54 | void lept_set_boolean(lept_value* v, int b); 55 | 56 | double lept_get_number(const lept_value* v); 57 | void lept_set_number(lept_value* v, double n); 58 | 59 | const char* lept_get_string(const lept_value* v); 60 | size_t lept_get_string_length(const lept_value* v); 61 | void lept_set_string(lept_value* v, const char* s, size_t len); 62 | 63 | size_t lept_get_array_size(const lept_value* v); 64 | lept_value* lept_get_array_element(const lept_value* v, size_t index); 65 | 66 | size_t lept_get_object_size(const lept_value* v); 67 | const char* lept_get_object_key(const lept_value* v, size_t index); 68 | size_t lept_get_object_key_length(const lept_value* v, size_t index); 69 | lept_value* lept_get_object_value(const lept_value* v, size_t index); 70 | 71 | #endif /* LEPTJSON_H__ */ 72 | -------------------------------------------------------------------------------- /tutorial06_answer/tutorial06_answer.md: -------------------------------------------------------------------------------- 1 | # 从零开始的 JSON 库教程(六):解析对象解答篇 2 | 3 | * Milo Yip 4 | * 2016/11/15 5 | 6 | 本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第六个单元解答篇。解答代码位于 [json-tutorial/tutorial06_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial06_answer)。 7 | 8 | ## 1. 重构 `lept_parse_string()` 9 | 10 | 这个「提取方法」重构练习很简单,只需要把原来调用 `lept_set_string` 的地方,改为写入参数变量。因此,原来的 `lept_parse_string()` 和 答案中的 `lept_parse_string_raw()` 的 diff 仅是两处: 11 | 12 | ~~~ 13 | 130,131c130,131 14 | < static int lept_parse_string(lept_context* c, lept_value* v) { 15 | < size_t head = c->top, len; 16 | --- 17 | > static int lept_parse_string_raw(lept_context* c, char** str, size_t* len) { 18 | > size_t head = c->top; 19 | 140,141c140,141 20 | < len = c->top - head; 21 | < lept_set_string(v, (const char*)lept_context_pop(c, len), len); 22 | --- 23 | > *len = c->top - head; 24 | > *str = lept_context_pop(c, *len); 25 | ~~~ 26 | 27 | 以 TDD 方式开发软件,因为有单元测试确保软件的正确性,面对新需求可以安心重构,改善软件架构。 28 | 29 | ## 2. 实现 `lept_parse_object()` 30 | 31 | 有了 `lept_parse_array()` 的经验,实现 `lept_parse_object()` 几乎是一样的,分别只是每个迭代要多处理一个键和冒号。我们把这个实现过程分为 5 步曲。 32 | 33 | 第 1 步是利用刚才重构出来的 `lept_parse_string_raw()` 去解析键的字符串。由于 `lept_parse_string_raw()` 假设第一个字符为 `"`,我们要先作校检,失败则要返回 `LEPT_PARSE_MISS_KEY` 错误。若字符串解析成功,它会把结果存储在我们的栈之中,需要把结果写入临时 `lept_member` 的 `k` 和 `klen` 字段中: 34 | 35 | ~~~c 36 | static int lept_parse_object(lept_context* c, lept_value* v) { 37 | size_t i, size; 38 | lept_member m; 39 | int ret; 40 | /* ... */ 41 | m.k = NULL; 42 | size = 0; 43 | for (;;) { 44 | char* str; 45 | lept_init(&m.v); 46 | /* 1. parse key */ 47 | if (*c->json != '"') { 48 | ret = LEPT_PARSE_MISS_KEY; 49 | break; 50 | } 51 | if ((ret = lept_parse_string_raw(c, &str, &m.klen)) != LEPT_PARSE_OK) 52 | break; 53 | memcpy(m.k = (char*)malloc(m.klen + 1), str, m.klen); 54 | m.k[m.klen] = '\0'; 55 | /* 2. parse ws colon ws */ 56 | /* ... */ 57 | } 58 | /* 5. Pop and free members on the stack */ 59 | /* ... */ 60 | } 61 | ~~~ 62 | 63 | 第 2 步是解析冒号,冒号前后可有空白字符: 64 | 65 | ~~~c 66 | /* 2. parse ws colon ws */ 67 | lept_parse_whitespace(c); 68 | if (*c->json != ':') { 69 | ret = LEPT_PARSE_MISS_COLON; 70 | break; 71 | } 72 | c->json++; 73 | lept_parse_whitespace(c); 74 | ~~~ 75 | 76 | 第 3 步是解析任意的 JSON 值。这部分与解析数组一样,递归调用 `lept_parse_value()`,把结果写入临时 `lept_member` 的 `v` 字段,然后把整个 `lept_member` 压入栈: 77 | 78 | ~~~c 79 | /* 3. parse value */ 80 | if ((ret = lept_parse_value(c, &m.v)) != LEPT_PARSE_OK) 81 | break; 82 | memcpy(lept_context_push(c, sizeof(lept_member)), &m, sizeof(lept_member)); 83 | size++; 84 | m.k = NULL; /* ownership is transferred to member on stack */ 85 | ~~~ 86 | 87 | 但有一点要注意,如果之前缺乏冒号,或是这里解析值失败,在函数返回前我们要释放 `m.k`。如果我们成功地解析整个成员,那么就要把 `m.k` 设为空指针,其意义是说明该键的字符串的拥有权已转移至栈,之后如遇到错误,我们不会重覆释放栈里成员的键和这个临时成员的键。 88 | 89 | 第 4 步,解析逗号或右花括号。遇上右花括号的话,当前的 JSON 对象就解析完结了,我们把栈上的成员复制至结果,并直接返回: 90 | 91 | ~~~c 92 | /* 4. parse ws [comma | right-curly-brace] ws */ 93 | lept_parse_whitespace(c); 94 | if (*c->json == ',') { 95 | c->json++; 96 | lept_parse_whitespace(c); 97 | } 98 | else if (*c->json == '}') { 99 | size_t s = sizeof(lept_member) * size; 100 | c->json++; 101 | v->type = LEPT_OBJECT; 102 | v->u.o.size = size; 103 | memcpy(v->u.o.m = (lept_member*)malloc(s), lept_context_pop(c, s), s); 104 | return LEPT_PARSE_OK; 105 | } 106 | else { 107 | ret = LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET; 108 | break; 109 | } 110 | ~~~ 111 | 112 | 最后,当 `for (;;)` 中遇到任何错误便会到达这第 5 步,要释放临时的 key 字符串及栈上的成员: 113 | 114 | ~~~c 115 | /* 5. Pop and free members on the stack */ 116 | free(m.k); 117 | for (i = 0; i < size; i++) { 118 | lept_member* m = (lept_member*)lept_context_pop(c, sizeof(lept_member)); 119 | free(m->k); 120 | lept_free(&m->v); 121 | } 122 | v->type = LEPT_NULL; 123 | return ret; 124 | ~~~ 125 | 126 | 注意我们不需要先检查 `m.k != NULL`,因为 `free(NULL)` 是完全合法的。 127 | 128 | ## 3. 释放内存 129 | 130 | 使用工具检测内存泄漏时,我们会发现以下这行代码造成内存泄漏: 131 | 132 | ~~~c 133 | memcpy(v->u.o.m = (lept_member*)malloc(s), lept_context_pop(c, s), s); 134 | ~~~ 135 | 136 | 类似数组,我们也需要在 `lept_free()` 释放 JSON 对象的成员(包括键及值): 137 | 138 | ~~~c 139 | void lept_free(lept_value* v) { 140 | size_t i; 141 | assert(v != NULL); 142 | switch (v->type) { 143 | /* ... */ 144 | case LEPT_OBJECT: 145 | for (i = 0; i < v->u.o.size; i++) { 146 | free(v->u.o.m[i].k); 147 | lept_free(&v->u.o.m[i].v); 148 | } 149 | free(v->u.o.m); 150 | break; 151 | default: break; 152 | } 153 | v->type = LEPT_NULL; 154 | } 155 | ~~~ 156 | 157 | ## 4. 总结 158 | 159 | 至此,你已实现一个完整的 JSON 解析器,可解析任何合法的 JSON。统计一下,不计算空行及注释,现时 `leptjson.c` 只有 405 行代码,`lept_json.h` 54 行,`test.c` 309 行。 160 | 161 | 另一方面,一些程序也需要生成 JSON。也许最初读者会以为生成 JSON 只需直接 `sprintf()/fprintf()` 就可以,但深入了解 JSON 的语法之后,我们应该知道 JSON 语法还是需做一些处理,例如字符串的转义、数字的格式等。然而,实现生成器还是要比解析器容易得多。而且,假设我们有一个正确的解析器,可以简单使用 roundtrip 方式实现测试。请期待下回分解。 162 | 163 | 如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 164 | -------------------------------------------------------------------------------- /tutorial07/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.6) 2 | project (leptjson_test C) 3 | 4 | if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") 6 | endif() 7 | 8 | add_library(leptjson leptjson.c) 9 | add_executable(leptjson_test test.c) 10 | target_link_libraries(leptjson_test leptjson) 11 | -------------------------------------------------------------------------------- /tutorial07/images/makefile: -------------------------------------------------------------------------------- 1 | %.png: %.dot 2 | dot $< -Tpng -o $@ 3 | 4 | DOTFILES = $(basename $(wildcard *.dot)) 5 | all: $(addsuffix .png, $(DOTFILES)) 6 | -------------------------------------------------------------------------------- /tutorial07/images/parse_stringify.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | compound=true 3 | fontname="Inconsolata, Consolas" 4 | fontsize=10 5 | margin="0,0" 6 | ranksep=0.3 7 | penwidth=0.5 8 | 9 | node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] 10 | edge [fontname="Inconsolata, Consolas", fontsize=10, arrowhead=normal] 11 | 12 | { 13 | node [shape=record, fontsize="8", margin="0.04", height=0.2, color=gray] 14 | json [label="\{|\"|p|r|o|j|e|c|t|\"|:|\"|l|e|p|t|j|s|o|n|\"|,|\"|s|t|a|r|s|\"|:|1|0|\}"] 15 | } 16 | 17 | subgraph cluster1 { 18 | margin="10,10" 19 | labeljust="left" 20 | label = "lept_value tree" 21 | style=filled 22 | fillcolor=gray95 23 | node [shape=Mrecord, style=filled, colorscheme=spectral7] 24 | 25 | root [label="{object|}", fillcolor=3] 26 | 27 | { 28 | project [label="{key|\"project\"}", fillcolor=5] 29 | leptjson [label="{string|\"leptjson\"}", fillcolor=5] 30 | stars [label="{key|\"stars\"}", fillcolor=5] 31 | ten [label="{number|10}", fillcolor=6] 32 | } 33 | 34 | edge [arrowhead=vee] 35 | root -> { project; stars } 36 | 37 | edge [arrowhead="none"] 38 | project -> leptjson 39 | stars -> ten 40 | } 41 | 42 | json -> root [label=" lept_parse() ", lhead="cluster1"] 43 | root -> json [label=" lept_stringify() ", ltail="cluster1"] 44 | } -------------------------------------------------------------------------------- /tutorial07/images/parse_stringify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/json-tutorial/645d80e849137c85e5a9e54982a9fb50a462e5ea/tutorial07/images/parse_stringify.png -------------------------------------------------------------------------------- /tutorial07/leptjson.h: -------------------------------------------------------------------------------- 1 | #ifndef LEPTJSON_H__ 2 | #define LEPTJSON_H__ 3 | 4 | #include /* size_t */ 5 | 6 | typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; 7 | 8 | typedef struct lept_value lept_value; 9 | typedef struct lept_member lept_member; 10 | 11 | struct lept_value { 12 | union { 13 | struct { lept_member* m; size_t size; }o; /* object: members, member count */ 14 | struct { lept_value* e; size_t size; }a; /* array: elements, element count */ 15 | struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ 16 | double n; /* number */ 17 | }u; 18 | lept_type type; 19 | }; 20 | 21 | struct lept_member { 22 | char* k; size_t klen; /* member key string, key string length */ 23 | lept_value v; /* member value */ 24 | }; 25 | 26 | enum { 27 | LEPT_PARSE_OK = 0, 28 | LEPT_PARSE_EXPECT_VALUE, 29 | LEPT_PARSE_INVALID_VALUE, 30 | LEPT_PARSE_ROOT_NOT_SINGULAR, 31 | LEPT_PARSE_NUMBER_TOO_BIG, 32 | LEPT_PARSE_MISS_QUOTATION_MARK, 33 | LEPT_PARSE_INVALID_STRING_ESCAPE, 34 | LEPT_PARSE_INVALID_STRING_CHAR, 35 | LEPT_PARSE_INVALID_UNICODE_HEX, 36 | LEPT_PARSE_INVALID_UNICODE_SURROGATE, 37 | LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, 38 | LEPT_PARSE_MISS_KEY, 39 | LEPT_PARSE_MISS_COLON, 40 | LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET 41 | }; 42 | 43 | #define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) 44 | 45 | int lept_parse(lept_value* v, const char* json); 46 | char* lept_stringify(const lept_value* v, size_t* length); 47 | 48 | void lept_free(lept_value* v); 49 | 50 | lept_type lept_get_type(const lept_value* v); 51 | 52 | #define lept_set_null(v) lept_free(v) 53 | 54 | int lept_get_boolean(const lept_value* v); 55 | void lept_set_boolean(lept_value* v, int b); 56 | 57 | double lept_get_number(const lept_value* v); 58 | void lept_set_number(lept_value* v, double n); 59 | 60 | const char* lept_get_string(const lept_value* v); 61 | size_t lept_get_string_length(const lept_value* v); 62 | void lept_set_string(lept_value* v, const char* s, size_t len); 63 | 64 | size_t lept_get_array_size(const lept_value* v); 65 | lept_value* lept_get_array_element(const lept_value* v, size_t index); 66 | 67 | size_t lept_get_object_size(const lept_value* v); 68 | const char* lept_get_object_key(const lept_value* v, size_t index); 69 | size_t lept_get_object_key_length(const lept_value* v, size_t index); 70 | lept_value* lept_get_object_value(const lept_value* v, size_t index); 71 | 72 | #endif /* LEPTJSON_H__ */ 73 | -------------------------------------------------------------------------------- /tutorial07/tutorial07.md: -------------------------------------------------------------------------------- 1 | # 从零开始的 JSON 库教程(七):生成器 2 | 3 | * Milo Yip 4 | * 2016/12/20 5 | 6 | 本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第七个单元。代码位于 [json-tutorial/tutorial07](https://github.com/miloyip/json-tutorial/blob/master/tutorial07)。 7 | 8 | 本单元内容: 9 | 10 | 1. [JSON 生成器](#1-json-生成器) 11 | 2. [再利用 lept_context 做动态数组](#2-再利用-lept_context-做动态数组) 12 | 3. [生成 null、false 和 true](#3-生成-nullfalse-和-true) 13 | 4. [生成数字](#4-生成数字) 14 | 5. [总结与练习](#5-总结与练习) 15 | 16 | ## 1. JSON 生成器 17 | 18 | 我们在前 6 个单元实现了一个合乎标准的 JSON 解析器,它把 JSON 文本解析成一个树形数据结构,整个结构以 `lept_value` 的节点组成。 19 | 20 | JSON 生成器(generator)负责相反的事情,就是把树形数据结构转换成 JSON 文本。这个过程又称为「字符串化(stringify)」。 21 | 22 | ![JSON 的解析与生成](images/parse_stringify.png) 23 | 24 | 相对于解析器,通常生成器更容易实现,而且生成器几乎不会造成运行时错误。因此,生成器的 API 设计为以下形式,直接返回 JSON 的字符串: 25 | 26 | ~~~c 27 | char* lept_stringify(const lept_value* v, size_t* length); 28 | ~~~ 29 | 30 | `length` 参数是可选的,它会存储 JSON 的长度,传入 `NULL` 可忽略此参数。使用方需负责用 `free()` 释放内存。 31 | 32 | 为了简单起见,我们不做换行、缩进等美化(prettify)处理,因此它生成的 JSON 会是单行、无空白字符的最紧凑形式。 33 | 34 | ## 2. 再利用 lept_context 做动态数组 35 | 36 | 在实现 JSON 解析时,我们加入了一个动态变长的堆栈,用于存储临时的解析结果。而现在,我们也需要存储生成的结果,所以最简单是再利用该数据结构,作为输出缓冲区。 37 | 38 | ~~~c 39 | #ifndef LEPT_PARSE_STRINGIFY_INIT_SIZE 40 | #define LEPT_PARSE_STRINGIFY_INIT_SIZE 256 41 | #endif 42 | 43 | int lept_stringify(const lept_value* v, char** json, size_t* length) { 44 | lept_context c; 45 | int ret; 46 | assert(v != NULL); 47 | assert(json != NULL); 48 | c.stack = (char*)malloc(c.size = LEPT_PARSE_STRINGIFY_INIT_SIZE); 49 | c.top = 0; 50 | if ((ret = lept_stringify_value(&c, v)) != LEPT_STRINGIFY_OK) { 51 | free(c.stack); 52 | *json = NULL; 53 | return ret; 54 | } 55 | if (length) 56 | *length = c.top; 57 | PUTC(&c, '\0'); 58 | *json = c.stack; 59 | return LEPT_STRINGIFY_OK; 60 | } 61 | ~~~ 62 | 63 | 生成根节点的值之后,我们还需要加入一个空字符作结尾。 64 | 65 | 如前所述,此 API 还提供了 `length` 可选参数,当传入非空指针时,就能获得生成 JSON 的长度。或许读者会疑问,为什么需要获得长度,我们不是可以用 `strlen()` 获得么?是的,因为 JSON 不会含有空字符(若 JSON 字符串中含空字符,必须转义为 `\u0000`),用 `strlen()` 是没有问题的。但这样做会带来不必要的性能消耗,理想地是避免调用方有额外消耗。 66 | 67 | ## 3. 生成 null、false 和 true 68 | 69 | 接下来,我们生成最简单的 JSON 类型,就是 3 种 JSON 字面值。为贯彻 TDD,先写测试: 70 | 71 | ~~~c 72 | #define TEST_ROUNDTRIP(json)\ 73 | do {\ 74 | lept_value v;\ 75 | char* json2;\ 76 | size_t length;\ 77 | lept_init(&v);\ 78 | EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ 79 | EXPECT_EQ_INT(LEPT_STRINGIFY_OK, lept_stringify(&v, &json2, &length));\ 80 | EXPECT_EQ_STRING(json, json2, length);\ 81 | lept_free(&v);\ 82 | free(json2);\ 83 | } while(0) 84 | 85 | static void test_stringify() { 86 | TEST_ROUNDTRIP("null"); 87 | TEST_ROUNDTRIP("false"); 88 | TEST_ROUNDTRIP("true"); 89 | /* ... */ 90 | } 91 | ~~~ 92 | 93 | 这里我们采用一个最简单的测试方式,把一个 JSON 解析,然后再生成另一 JSON,逐字符比较两个 JSON 是否一模一样。这种测试可称为往返(roundtrip)测试。但需要注意,同一个 JSON 的内容可以有多种不同的表示方式,例如可以插入不定数量的空白字符,数字 `1.0` 和 `1` 也是等价的。所以另一种测试方式,是比较两次解析的结果(`lept_value` 的树)是否相同,此功能将会在下一单元讲解。 94 | 95 | 然后,我们实现 `lept_stringify_value`,加入一个 `PUTS()` 宏去输出字符串: 96 | 97 | ~~~c 98 | #define PUTS(c, s, len) memcpy(lept_context_push(c, len), s, len) 99 | 100 | static int lept_stringify_value(lept_context* c, const lept_value* v) { 101 | size_t i; 102 | int ret; 103 | switch (v->type) { 104 | case LEPT_NULL: PUTS(c, "null", 4); break; 105 | case LEPT_FALSE: PUTS(c, "false", 5); break; 106 | case LEPT_TRUE: PUTS(c, "true", 4); break; 107 | /* ... */ 108 | } 109 | return LEPT_STRINGIFY_OK; 110 | } 111 | ~~~ 112 | 113 | ## 4. 生成数字 114 | 115 | 为了简单起见,我们使用 `sprintf("%.17g", ...)` 来把浮点数转换成文本。`"%.17g"` 是足够把双精度浮点转换成可还原的文本。 116 | 117 | 最简单的实现方式可能是这样的: 118 | 119 | ~~~c 120 | case LEPT_NUMBER: 121 | { 122 | char buffer[32]; 123 | int length = sprintf(buffer, "%.17g", v->u.n); 124 | PUTS(c, buffer, length); 125 | } 126 | break; 127 | ~~~ 128 | 129 | 但这样需要在 `PUTS()` 中做一次 `memcpy()`,实际上我们可以避免这次复制,只需要生成的时候直接写进 `c` 里的堆栈,然后再按实际长度调查 `c->top`: 130 | 131 | ~~~c 132 | case LEPT_NUMBER: 133 | { 134 | char* buffer = lept_context_push(c, 32); 135 | int length = sprintf(buffer, "%.17g", v->u.n); 136 | c->top -= 32 - length; 137 | } 138 | break; 139 | ~~~ 140 | 141 | 因每个临时变量只用了一次,我们可以把代码压缩成一行: 142 | 143 | ~~~c 144 | case LEPT_NUMBER: 145 | c->top -= 32 - sprintf(lept_context_push(c, 32), "%.17g", v->u.n); 146 | break; 147 | ~~~ 148 | 149 | ## 5. 总结与练习 150 | 151 | 我们在此单元中简介了 JSON 的生成功能和 leptjson 中的实现方式。 152 | 153 | leptjson 重复利用了 `lept_context` 中的数据结构作为输出缓冲,可以节省代码量。 154 | 155 | 生成通常比解析简单(一个例外是 RapidJSON 自行实现了浮点数至字符串的算法),余下的 3 种 JSON 类型就当作练习吧: 156 | 157 | 1. 由于有两个地方需要生成字符串(JSON 字符串和对象类型),所以先实现 `lept_stringify_string()`。注意,字符串的语法比较复杂,一些字符必须转义,其他少于 `0x20` 的字符需要转义为 `\u00xx` 形式。 158 | 159 | 2. 直接在 `lept_stringify_value()` 的 `switch` 内实现 JSON 数组和对象类型的生成。这些实现里都会递归调用 `lept_stringify_value()`。 160 | 161 | 3. 在你的 `lept_stringify_string()` 是否使用了多次 `PUTC()`?如果是,它每次输出一个字符时,都要检测缓冲区是否有足够空间(不够时需扩展)。能否优化这部分的性能?这种优化有什么代价么? 162 | 163 | 如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 164 | -------------------------------------------------------------------------------- /tutorial07_answer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.6) 2 | project (leptjson_test C) 3 | 4 | if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") 6 | endif() 7 | 8 | add_library(leptjson leptjson.c) 9 | add_executable(leptjson_test test.c) 10 | target_link_libraries(leptjson_test leptjson) 11 | -------------------------------------------------------------------------------- /tutorial07_answer/leptjson.h: -------------------------------------------------------------------------------- 1 | #ifndef LEPTJSON_H__ 2 | #define LEPTJSON_H__ 3 | 4 | #include /* size_t */ 5 | 6 | typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; 7 | 8 | typedef struct lept_value lept_value; 9 | typedef struct lept_member lept_member; 10 | 11 | struct lept_value { 12 | union { 13 | struct { lept_member* m; size_t size; }o; /* object: members, member count */ 14 | struct { lept_value* e; size_t size; }a; /* array: elements, element count */ 15 | struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ 16 | double n; /* number */ 17 | }u; 18 | lept_type type; 19 | }; 20 | 21 | struct lept_member { 22 | char* k; size_t klen; /* member key string, key string length */ 23 | lept_value v; /* member value */ 24 | }; 25 | 26 | enum { 27 | LEPT_PARSE_OK = 0, 28 | LEPT_PARSE_EXPECT_VALUE, 29 | LEPT_PARSE_INVALID_VALUE, 30 | LEPT_PARSE_ROOT_NOT_SINGULAR, 31 | LEPT_PARSE_NUMBER_TOO_BIG, 32 | LEPT_PARSE_MISS_QUOTATION_MARK, 33 | LEPT_PARSE_INVALID_STRING_ESCAPE, 34 | LEPT_PARSE_INVALID_STRING_CHAR, 35 | LEPT_PARSE_INVALID_UNICODE_HEX, 36 | LEPT_PARSE_INVALID_UNICODE_SURROGATE, 37 | LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, 38 | LEPT_PARSE_MISS_KEY, 39 | LEPT_PARSE_MISS_COLON, 40 | LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET 41 | }; 42 | 43 | #define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) 44 | 45 | int lept_parse(lept_value* v, const char* json); 46 | char* lept_stringify(const lept_value* v, size_t* length); 47 | 48 | void lept_free(lept_value* v); 49 | 50 | lept_type lept_get_type(const lept_value* v); 51 | 52 | #define lept_set_null(v) lept_free(v) 53 | 54 | int lept_get_boolean(const lept_value* v); 55 | void lept_set_boolean(lept_value* v, int b); 56 | 57 | double lept_get_number(const lept_value* v); 58 | void lept_set_number(lept_value* v, double n); 59 | 60 | const char* lept_get_string(const lept_value* v); 61 | size_t lept_get_string_length(const lept_value* v); 62 | void lept_set_string(lept_value* v, const char* s, size_t len); 63 | 64 | size_t lept_get_array_size(const lept_value* v); 65 | lept_value* lept_get_array_element(const lept_value* v, size_t index); 66 | 67 | size_t lept_get_object_size(const lept_value* v); 68 | const char* lept_get_object_key(const lept_value* v, size_t index); 69 | size_t lept_get_object_key_length(const lept_value* v, size_t index); 70 | lept_value* lept_get_object_value(const lept_value* v, size_t index); 71 | 72 | #endif /* LEPTJSON_H__ */ 73 | -------------------------------------------------------------------------------- /tutorial07_answer/tutorial07_answer.md: -------------------------------------------------------------------------------- 1 | # 从零开始的 JSON 库教程(七):生成器解答篇 2 | 3 | * Milo Yip 4 | * 2017/1/5 5 | 6 | 本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第七个单元解答篇。解答代码位于 [json-tutorial/tutorial07_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial07_answer)。 7 | 8 | ## 1. 生成字符串 9 | 10 | 我们需要对一些字符进行转义,最简单的实现如下: 11 | 12 | ~~~c 13 | static void lept_stringify_string(lept_context* c, const char* s, size_t len) { 14 | size_t i; 15 | assert(s != NULL); 16 | PUTC(c, '"'); 17 | for (i = 0; i < len; i++) { 18 | unsigned char ch = (unsigned char)s[i]; 19 | switch (ch) { 20 | case '\"': PUTS(c, "\\\"", 2); break; 21 | case '\\': PUTS(c, "\\\\", 2); break; 22 | case '\b': PUTS(c, "\\b", 2); break; 23 | case '\f': PUTS(c, "\\f", 2); break; 24 | case '\n': PUTS(c, "\\n", 2); break; 25 | case '\r': PUTS(c, "\\r", 2); break; 26 | case '\t': PUTS(c, "\\t", 2); break; 27 | default: 28 | if (ch < 0x20) { 29 | char buffer[7]; 30 | sprintf(buffer, "\\u%04X", ch); 31 | PUTS(c, buffer, 6); 32 | } 33 | else 34 | PUTC(c, s[i]); 35 | } 36 | } 37 | PUTC(c, '"'); 38 | } 39 | 40 | static void lept_stringify_value(lept_context* c, const lept_value* v) { 41 | switch (v->type) { 42 | /* ... */ 43 | case LEPT_STRING: lept_stringify_string(c, v->u.s.s, v->u.s.len); break; 44 | /* ... */ 45 | } 46 | } 47 | ~~~ 48 | 49 | 注意到,十六进位输出的字母可以用大写或小写,我们这里选择了大写,所以 roundtrip 测试时也用大写。但这个并不是必然的,输出小写(用 `"\\u%04x"`)也可以。 50 | 51 | ## 2. 生成数组和对象 52 | 53 | 生成数组也是非常简单,只要输出 `[` 和 `]`,中间对逐个子值递归调用 `lept_stringify_value()`。只要注意在第一个元素后才加入 `,`。而对象也仅是多了一个键和 `:`。 54 | 55 | ~~~cs 56 | static void lept_stringify_value(lept_context* c, const lept_value* v) { 57 | size_t i; 58 | switch (v->type) { 59 | /* ... */ 60 | case LEPT_ARRAY: 61 | PUTC(c, '['); 62 | for (i = 0; i < v->u.a.size; i++) { 63 | if (i > 0) 64 | PUTC(c, ','); 65 | lept_stringify_value(c, &v->u.a.e[i]); 66 | } 67 | PUTC(c, ']'); 68 | break; 69 | case LEPT_OBJECT: 70 | PUTC(c, '{'); 71 | for (i = 0; i < v->u.o.size; i++) { 72 | if (i > 0) 73 | PUTC(c, ','); 74 | lept_stringify_string(c, v->u.o.m[i].k, v->u.o.m[i].klen); 75 | PUTC(c, ':'); 76 | lept_stringify_value(c, &v->u.o.m[i].v); 77 | } 78 | PUTC(c, '}'); 79 | break; 80 | /* ... */ 81 | } 82 | } 83 | ~~~ 84 | 85 | ## 3. 优化 `lept_stringify_string()` 86 | 87 | 最后,我们讨论一下优化。上面的 `lept_stringify_string()` 实现中,每次输出一个字符/字符串,都要调用 `lept_context_push()`。如果我们使用一些性能剖测工具,也可能会发现这个函数消耗较多 CPU。 88 | 89 | ~~~c 90 | static void* lept_context_push(lept_context* c, size_t size) { 91 | void* ret; 92 | assert(size > 0); 93 | if (c->top + size >= c->size) { // (1) 94 | if (c->size == 0) 95 | c->size = LEPT_PARSE_STACK_INIT_SIZE; 96 | while (c->top + size >= c->size) 97 | c->size += c->size >> 1; /* c->size * 1.5 */ 98 | c->stack = (char*)realloc(c->stack, c->size); 99 | } 100 | ret = c->stack + c->top; // (2) 101 | c->top += size; // (3) 102 | return ret; // (4) 103 | } 104 | ~~~ 105 | 106 | 中间最花费时间的,应该会是 (1),需要计算而且作分支检查。即使使用 C99 的 `inline` 关键字(或使用宏)去减少函数调用的开销,这个分支也无法避免。 107 | 108 | 所以,一个优化的点子是,预先分配足够的内存,每次加入字符就不用做这个检查了。但多大的内存才足够呢?我们可以看到,每个字符可生成最长的形式是 `\u00XX`,占 6 个字符,再加上前后两个双引号,也就是共 `len * 6 + 2` 个输出字符。那么,使用 `char* p = lept_context_push()` 作一次分配后,便可以用 `*p++ = c` 去输出字符了。最后,再按实际输出量调整堆栈指针。 109 | 110 | 另一个小优化点,是自行编写十六进位输出,避免了 `printf()` 内解析格式的开销。 111 | 112 | ~~~c 113 | static void lept_stringify_string(lept_context* c, const char* s, size_t len) { 114 | static const char hex_digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; 115 | size_t i, size; 116 | char* head, *p; 117 | assert(s != NULL); 118 | p = head = lept_context_push(c, size = len * 6 + 2); /* "\u00xx..." */ 119 | *p++ = '"'; 120 | for (i = 0; i < len; i++) { 121 | unsigned char ch = (unsigned char)s[i]; 122 | switch (ch) { 123 | case '\"': *p++ = '\\'; *p++ = '\"'; break; 124 | case '\\': *p++ = '\\'; *p++ = '\\'; break; 125 | case '\b': *p++ = '\\'; *p++ = 'b'; break; 126 | case '\f': *p++ = '\\'; *p++ = 'f'; break; 127 | case '\n': *p++ = '\\'; *p++ = 'n'; break; 128 | case '\r': *p++ = '\\'; *p++ = 'r'; break; 129 | case '\t': *p++ = '\\'; *p++ = 't'; break; 130 | default: 131 | if (ch < 0x20) { 132 | *p++ = '\\'; *p++ = 'u'; *p++ = '0'; *p++ = '0'; 133 | *p++ = hex_digits[ch >> 4]; 134 | *p++ = hex_digits[ch & 15]; 135 | } 136 | else 137 | *p++ = s[i]; 138 | } 139 | } 140 | *p++ = '"'; 141 | c->top -= size - (p - head); 142 | } 143 | ~~~ 144 | 145 | 要注意的是,很多优化都是有代价的。第一个优化采取空间换时间的策略,对于只含一个字符串的JSON,很可能会分配多 6 倍内存;但对于正常含多个值的 JSON,多分配的内存可在之后的值所利用,不会造成太多浪费。 146 | 147 | 而第二个优化的缺点,就是有稍增加了一点程序体积。也许有人会问,为什么 `hex_digits` 不用字符串字面量 `"0123456789ABCDEF"`?其实是可以的,但这会多浪费 1 个字节(实际因数据对齐可能会浪费 4 个或更多)。 148 | 149 | ## 4. 总结 150 | 151 | 我们用 80 行左右的代码就实现了 JSON 生成器,并尝试了做一些简单的优化。除了这种最简单的功能,有一些 JSON 库还会提供一些美化功能,即加入缩进及换行。另外,有一些应用可能需要大量输出数字,那么就可能需要优化数字的输出。这方面可考虑 C++ 开源库 [double-conversion](https://github.com/google/double-conversion),以及参考本人另一篇文章《[RapidJSON 代码剖析(四):优化 Grisu](https://zhuanlan.zhihu.com/p/20092285)》。 152 | 153 | 现时数组和对象类型只有最基本的访问、修改函数,我们会在下一篇补完。 154 | 155 | 如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 156 | -------------------------------------------------------------------------------- /tutorial08/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.6) 2 | project (leptjson_test C) 3 | 4 | if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") 6 | endif() 7 | 8 | add_library(leptjson leptjson.c) 9 | add_executable(leptjson_test test.c) 10 | target_link_libraries(leptjson_test leptjson) 11 | -------------------------------------------------------------------------------- /tutorial08/leptjson.h: -------------------------------------------------------------------------------- 1 | #ifndef LEPTJSON_H__ 2 | #define LEPTJSON_H__ 3 | 4 | #include /* size_t */ 5 | 6 | typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; 7 | 8 | #define LEPT_KEY_NOT_EXIST ((size_t)-1) 9 | 10 | typedef struct lept_value lept_value; 11 | typedef struct lept_member lept_member; 12 | 13 | struct lept_value { 14 | union { 15 | struct { lept_member* m; size_t size, capacity; }o; /* object: members, member count, capacity */ 16 | struct { lept_value* e; size_t size, capacity; }a; /* array: elements, element count, capacity */ 17 | struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ 18 | double n; /* number */ 19 | }u; 20 | lept_type type; 21 | }; 22 | 23 | struct lept_member { 24 | char* k; size_t klen; /* member key string, key string length */ 25 | lept_value v; /* member value */ 26 | }; 27 | 28 | enum { 29 | LEPT_PARSE_OK = 0, 30 | LEPT_PARSE_EXPECT_VALUE, 31 | LEPT_PARSE_INVALID_VALUE, 32 | LEPT_PARSE_ROOT_NOT_SINGULAR, 33 | LEPT_PARSE_NUMBER_TOO_BIG, 34 | LEPT_PARSE_MISS_QUOTATION_MARK, 35 | LEPT_PARSE_INVALID_STRING_ESCAPE, 36 | LEPT_PARSE_INVALID_STRING_CHAR, 37 | LEPT_PARSE_INVALID_UNICODE_HEX, 38 | LEPT_PARSE_INVALID_UNICODE_SURROGATE, 39 | LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, 40 | LEPT_PARSE_MISS_KEY, 41 | LEPT_PARSE_MISS_COLON, 42 | LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET 43 | }; 44 | 45 | #define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) 46 | 47 | int lept_parse(lept_value* v, const char* json); 48 | char* lept_stringify(const lept_value* v, size_t* length); 49 | 50 | void lept_copy(lept_value* dst, const lept_value* src); 51 | void lept_move(lept_value* dst, lept_value* src); 52 | void lept_swap(lept_value* lhs, lept_value* rhs); 53 | 54 | void lept_free(lept_value* v); 55 | 56 | lept_type lept_get_type(const lept_value* v); 57 | int lept_is_equal(const lept_value* lhs, const lept_value* rhs); 58 | 59 | #define lept_set_null(v) lept_free(v) 60 | 61 | int lept_get_boolean(const lept_value* v); 62 | void lept_set_boolean(lept_value* v, int b); 63 | 64 | double lept_get_number(const lept_value* v); 65 | void lept_set_number(lept_value* v, double n); 66 | 67 | const char* lept_get_string(const lept_value* v); 68 | size_t lept_get_string_length(const lept_value* v); 69 | void lept_set_string(lept_value* v, const char* s, size_t len); 70 | 71 | void lept_set_array(lept_value* v, size_t capacity); 72 | size_t lept_get_array_size(const lept_value* v); 73 | size_t lept_get_array_capacity(const lept_value* v); 74 | void lept_reserve_array(lept_value* v, size_t capacity); 75 | void lept_shrink_array(lept_value* v); 76 | void lept_clear_array(lept_value* v); 77 | lept_value* lept_get_array_element(lept_value* v, size_t index); 78 | lept_value* lept_pushback_array_element(lept_value* v); 79 | void lept_popback_array_element(lept_value* v); 80 | lept_value* lept_insert_array_element(lept_value* v, size_t index); 81 | void lept_erase_array_element(lept_value* v, size_t index, size_t count); 82 | 83 | void lept_set_object(lept_value* v, size_t capacity); 84 | size_t lept_get_object_size(const lept_value* v); 85 | size_t lept_get_object_capacity(const lept_value* v); 86 | void lept_reserve_object(lept_value* v, size_t capacity); 87 | void lept_shrink_object(lept_value* v); 88 | void lept_clear_object(lept_value* v); 89 | const char* lept_get_object_key(const lept_value* v, size_t index); 90 | size_t lept_get_object_key_length(const lept_value* v, size_t index); 91 | lept_value* lept_get_object_value(lept_value* v, size_t index); 92 | size_t lept_find_object_index(const lept_value* v, const char* key, size_t klen); 93 | lept_value* lept_find_object_value(lept_value* v, const char* key, size_t klen); 94 | lept_value* lept_set_object_value(lept_value* v, const char* key, size_t klen); 95 | void lept_remove_object_value(lept_value* v, size_t index); 96 | 97 | #endif /* LEPTJSON_H__ */ 98 | --------------------------------------------------------------------------------