json 前言 在GitHub上找来练手的项目
在这里记录一下自己的路上遇到的难点和知识吧
01 Cmake创建项目(windows)
将这个设立为 启动项
单元测试 一般来说,软件开发是以周期进行,加入一个功能,再写关于该功能的单元测试。但也有另一种软件开发方法论,称为测试驱动开发(test-driven development, TDD),它的主要循环步骤是:
加入一个测试。
运行所有测试,新的测试应该会失败。
编写实现代码。
运行所有测试,若有测试失败回到3。
重构代码。
回到 1。
TDD 是先写测试,再实现功能。好处是实现只会刚好满足测试,而不会写了一些不需要的代码,或是没有被测试的代码。
1 2 3 4 5 6 7 8 9 10 11 12 #define EXPECT_EQ_BASE(equality, expect , actual , format) \ do {\ test_count++;\ if (equality)\ test_pass++;\ else {\ fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n" , __FILE__, __LINE__, expect , actual );\ main_ret = 1 ;\ }\ } while (0 ) #define EXPECT_EQ_INT(expect , actual ) EXPECT_EQ_BASE((expect ) == (actual ), expect , actual , "%d" )
简单例子
1 2 3 4 5 6 static void test_parse_null() { lept_value v; v.type = LEPT_FALSE; EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse (&v , "null" ) ); EXPECT_EQ_INT(LEPT_NULL, lept_get_type (&v ) ); }
02 简化单元测试 1 2 3 4 5 6 7 8 9 10 11 12 #define TEST_ERROR(error , json ) \ do {\ lept_value v;\ v.type = LEPT_FALSE;\ EXPECT_EQ_INT(error , lept_parse (&v , json ) );\ EXPECT_EQ_INT(LEPT_NULL, lept_get_type (&v ) );\ } while (0 ) static void test_parse_expect_value() { TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, "" ) ; TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " " ) ; }
number 解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <errno.h> #include <math.h> static int lept_parse_number (lept_context* c, lept_value* v) { char * end; const char * p = c->json; if (*p == '-' )p++; if (*p == '0' )p++; else { if (!ISDIGIT1TO9 (*p))return LEPT_PARSE_INVALID_VALUE; while (ISDIGIT (*p)) p++; } if (*p == '.' ) { p++; if (!ISDIGIT (*p))return LEPT_PARSE_INVALID_VALUE; while (ISDIGIT (*p)) p++; } if (*p == 'e' || *p == 'E' ) { p++; if (*p == '+' || *p == '-' )p++; if (!ISDIGIT (*p))return LEPT_PARSE_INVALID_VALUE; while (ISDIGIT (*p)) p++; } errno = 0 ; v->n = strtod (c->json, NULL ); if (errno == ERANGE && v->n == HUGE_VAL) return LEPT_PARSE_NUMBER_TOO_BIG; c->json = p; v->type = LEPT_NUMBER; return LEPT_PARSE_OK; }
errno 用于判断是否有溢出 ,当溢出后 errno==ERANGE
<math.h> 常量
说明
HUGE_VALF
扩展为表示溢出的正浮点表达式
HUGE_VAL
扩展为表示溢出的正数双表达式,不一定表示为浮点数
HUGE_VALL
扩展为正数long double表达式,表示溢出,不一定表示为float或double
03 字符串内存管理 C++ new 申请
delete 释放
c <stdlib.h>
中的 malloc()
、realloc()
和 free()
来分配/释放内存。
差别
new关键字是C++的一部分
malloc是由C库提供的函数
new以具体类型为单位进行内存分配
malloc以字节为单位进行内存分配
new在申请单个类型变量时可进行初始化
malloc不具备内存初始化的特性
缓冲区和堆栈 解析字符串(以及之后的数组、对象)时,需要把解析的结果先储存在一个临时的缓冲区,最后再用 lept_set_string()
把缓冲区的结果设进值之中。在完成解析一个字符串之前,这个缓冲区的大小是不能预知的。因此,我们可以采用动态数组(dynamic array)这种数据结构,即数组空间不足时,能自动扩展。C++ 标准库的 std::vector
也是一种动态数组。
如果每次解析字符串时,都重新建一个动态数组,那么是比较耗时的。我们可以重用这个动态数组,每次解析 JSON 时就只需要创建一个。而且我们将会发现,无论是解析字符串、数组或对象,我们也只需要以先进后出的方式访问这个动态数组。换句话说,我们需要一个动态的堆栈(stack)数据结构。
创建一个动态堆栈
1 2 3 4 5 typedef struct { const char * json; char * stack; size_t size, top; }lept_context;
size
是堆栈容量
top
是栈顶的位置
释放的时候要把全部弹出
因为该堆栈以字节储存,可压入任意字节数量的内存 (参考 RapidJSON 代码剖析(一):混合任意类型的堆栈 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0) #ifndef LEPT_PARSE_STACK_INIT_SIZE #define LEPT_PARSE_STACK_INIT_SIZE 256 #endif static void * lept_context_push (lept_context* c, size_t size) { void * ret; assert (size > 0 ); if (c->top + size >= c->size) { if (c->size == 0 ) c->size = LEPT_PARSE_STACK_INIT_SIZE; while (c->top + size >= c->size) c->size += c->size >> 1 ; c->stack = (char *)realloc (c->stack, c->size); } ret = c->stack + c->top; c->top += size; return ret; }static void * lept_context_pop (lept_context* c, size_t size) { assert (c->top >= size); return c->stack + (c->top -= size); }
1A. Windows 下的内存泄漏检测方法 在 Windows 下,可使用 Visual C++ 的 C Runtime Library(CRT) 检测内存泄漏 。
首先,我们在两个 .c 文件首行插入这一段代码:
1 2 3 4 #ifdef _WINDOWS #define _CRTDBG_MAP_ALLOC #include <crtdbg.h> #endif
并在 main()
开始位置插入:
1 2 3 4 int main () {#ifdef _WINDOWS _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);#endif
在 Debug 配置下按 F5 生成、开始调试程序,没有任何异样。
然后,我们删去 lept_set_boolean()
中的 lept_free(v)
:
1 2 3 4 void lept_set_boolean (lept_value* v, int b) { v->type = b ? LEPT_TRUE : LEPT_FALSE; }
再次按 F5 生成、开始调试程序,在输出会看到内存泄漏信息:
1 2 3 4 5 Detected memory leaks!Dumping objects ->C :\GitHub\json-tutorial\tutorial03 _answer\leptjson.c(212 ) : {79 } normal block at 0 x013 D9868 , 2 bytes long. Data : <a > 61 00 Object dump complete.
这正是我们在单元测试中,先设置字符串,然后设布尔值时没释放字符串所分配的内存。比较麻烦的是,它没有显示调用堆栈。从输出信息中 ... {79} ...
我们知道是第 79 次分配的内存做成问题,我们可以加上 _CrtSetBreakAlloc(79);
来调试,那么它便会在第 79 次时中断于分配调用的位置,那时候就能从调用堆栈去找出来龙去脉。
1B. Linux/OSX 下的内存泄漏检测方法 在 Linux、OS X 下,我们可以使用 valgrind 工具(用 apt-get install valgrind
、 brew install valgrind
)。我们完全不用修改代码,只要在命令行执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $ valgrind --leak-check=full ./leptjson_test ==22078 == Memcheck, a memory error detector ==22078 == Copyright (C) 2002 -2015 , and GNU GPL'd, by Julian Seward et al. ==2207 8== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info ==2207 8== Command: ./leptjson_test ==2207 8== --2207 8-- run: /usr/bin/dsymutil "./leptjson_test" 160/160 (100.00%) passed ==2207 8== ==2207 8== HEAP SUMMARY: ==2207 8== in use at exit: 27,728 bytes in 209 blocks ==2207 8== total heap usage: 301 allocs, 92 frees, 34,966 bytes allocated ==2207 8== ==2207 8== 2 bytes in 1 blocks are definitely lost in loss record 1 of 79 ==2207 8== at 0x100012 EBB: malloc (in /usr/local/Cellar/valgrind/3.11.0/lib/valgrind/vgpreload_memcheck-amd64-darwin.so) ==2207 8== by 0x100008 F36: lept_set_string (leptjson.c:208) ==2207 8== by 0x10000841 5: test_access_boolean (test.c:187) ==2207 8== by 0x10000184 9: test_parse (test.c:229) ==2207 8== by 0x100001 7A3: main (test.c:235) ==2207 8== ...
它发现了在 test_access_boolean()
中,由 lept_set_string()
分配的 2 个字节("a"
)泄漏了。
Valgrind 还有很多功能,例如可以发现未初始化变量。我们若在应用程序或测试程序中,忘了调用 lept_init(&v)
,那么 v.type
的值没被初始化,其值是不确定的(indeterministic),一些函数如果读取那个值就会出现问题:
1 2 3 4 5 6 static void test_access_boolean () { lept_value v; lept_set_string(&v, "a" , 1 ); ... }
这种错误有时候测试时能正确运行(刚好 v.type
被设为 0
),使我们误以为程序正确,而在发布后一些机器上却可能崩溃。这种误以为正确的假像是很危险的,我们可利用 valgrind 能自动测出来:
1 2 3 4 5 6 7 8 9 $ valgrind --leak-check =full ./leptjson_test.. . ==22174 == Conditional jump or move depends on uninitialised value(s) ==22174 == at 0x100008B5D: lept_free (leptjson.c:164) ==22174 == by 0x100008F26: lept_set_string (leptjson.c:207) ==22174 == by 0x1000083FE: test_access_boolean (test.c:187) ==22174 == by 0x100001839: test_parse (test.c:229) ==22174 == by 0x100001793: main (test.c:235) ==22174 ==
它发现 lept_free()
中依靠了一个未初始化的值来跳转,就是 v.type
,而错误是沿自 test_access_boolean()
。
编写单元测试时,应考虑哪些执行次序会有机会出错,例如内存相关的错误。然后我们可以利用 TDD 的步骤,先令测试失败(以内存工具检测),修正代码,再确认测试是否成功。
解析字符串 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 static int lept_parse_string(lept_context* c, lept_value* v) { size_t head = c->top, len; const char * p; EXPECT(c, '\"' ); p = c->json; for (;;) { char ch = *p++; switch (ch) { case '\\' : switch (*p++) { case '\"' : PUTC(c, '\"' ); break ; case '\\' : PUTC(c, '\\' ); break ; case '/' : PUTC(c, '/' ); break ; case 'b' : PUTC(c, '\b' ); break ; case 'f' : PUTC(c, '\f' ); break ; case 'n' : PUTC(c, '\n' ); break ; case 'r' : PUTC(c, '\r' ); break ; case 't' : PUTC(c, '\t' ); break ; default : c->top = head; return LEPT_PARSE_INVALID_STRING_ESCAPE; } break ; case '\"' : len = c->top - head; lept_set_string(v, (const char *)lept_context_pop(c, len), len); c->json = p; return LEPT_PARSE_OK; case '\0' : c->top = head; return LEPT_PARSE_MISS_QUOTATION_MARK; default : if ((unsigned char )ch < 0x20 ) { c->top = head; return LEPT_PARSE_INVALID_STRING_CHAR; } PUTC(c, ch); } } }
04 uft-8编码 UTF-8 的编码单元为 8 位(1 字节),每个码点编码成 1 至 4 个字节。它的编码方式很简单,按照码点的范围,把码点的二进位分拆成 1 至最多 4 个字节:
码点范围
码点位数
字节1
字节2
字节3
字节4
U+0000 ~ U+007F
7
0xxxxxxx
U+0080 ~ U+07FF
11
110xxxxx
10xxxxxx
U+0800 ~ U+FFFF
16
1110xxxx
10xxxxxx
10xxxxxx
U+10000 ~ U+10FFFF
21
11110xxx
10xxxxxx
10xxxxxx
10xxxxxx
这个编码方法的好处之一是,码点范围 U+0000 ~ U+007F 编码为一个字节,与 ASCII 编码兼容。这范围的 Unicode 码点也是和 ASCII 字符相同的。因此,一个 ASCII 文本也是一个 UTF-8 文本。
我们举一个例子解析多字节的情况,欧元符号 €
→ U+20AC:
U+20AC 在 U+0800 ~ U+FFFF 的范围内,应编码成 3 个字节。
U+20AC 的二进位为 10000010101100
3 个字节的情况我们要 16 位的码点,所以在前面补两个 0,成为 0010000010101100
按上表把二进位分成 3 组:0010, 000010, 101100
加上每个字节的前缀:11100010, 10000010, 10101100
用十六进位表示即:0xE2, 0x82, 0xAC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 static const char * lept_parse_hex4(const char * p , unsigned * u ) { *u = 0 ; for (int i = 0 ; i < 4 ; i++) { char ch = *p++; *u <<= 4 ; if (ch >= '0' && ch <= '9' ) *u |= ch - '0'; else if (ch >= 'A ' && ch <= 'F ') * u |= ch - ('A ' - 10); else if (ch >= 'a' && ch <= 'f') * u |= ch - ('a' - 10); else return NULL ; } return p; } static void lept_encode_utf8(lept_context * c , unsigned u ) { / * \TODO * / if (u <= 0x7F ) PUTC(c , u & 0xFF) ; else if (u <= 0x7FF ) { PUTC(c , 0xC0 | ((u >> 6) & 0xFF )); PUTC(c , 0x80 | (u & 0x3F) ); } else if (u <= 0xFFFF ) { PUTC(c , 0xE0 | ((u >> 12) & 0xFF )); PUTC(c , 0x80 | ((u >> 6) & 0x3F )); PUTC(c , 0x80 | (u & 0x3F) ); } else { assert (u <= 0x10FFFF ); PUTC(c , 0xF0 | ((u >> 18) & 0xFF )); PUTC(c , 0x80 | ((u >> 12) & 0x3F )); PUTC(c , 0x80 | ((u >> 6) & 0x3F )); PUTC(c , 0x80 | (u & 0x3F) ); } }
代理对 其实,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) 变换成真实的码点:
1 codepoint = 0 x10000 + (H − 0 xD800 ) × 0 x400 + (L − 0 xDC00 )
举个例子,高音谱号字符 𝄞
→ U+1D11E 不是 BMP 之内的字符。在 JSON 中可写成转义序列 \uD834\uDD1E
,我们解析第一个 \uD834
得到码点 U+D834,我们发现它是 U+D800 至 U+DBFF 内的码点,所以它是高代理项。然后我们解析下一个转义序列 \uDD1E
得到码点 U+DD1E,它在 U+DC00 至 U+DFFF 之内,是合法的低代理项。我们计算其码点:
1 2 3 4 5 6 H = 0xD834 , L = 0xDD1E codepoint = 0x10000 + (H − 0xD800 ) × 0x400 + (L − 0xDC00 ) = 0x10000 + (0xD834 - 0xD800 ) × 0x400 + (0xDD1E − 0xDC00 ) = 0x10000 + 0x34 × 0x400 + 0x11E = 0x10000 + 0xD000 + 0x11E = 0x1D11E
这样就得出这转义序列的码点,然后我们再把它编码成 UTF-8。如果只有高代理项而欠缺低代理项,或是低代理项不在合法码点范围,我们都返回 LEPT_PARSE_INVALID_UNICODE_SURROGATE
错误。如果 \u
后不是 4 位十六进位数字,则返回 LEPT_PARSE_INVALID_UNICODE_HEX
错误。
对于utf-8解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 static const char * lept_parse_hex4(const char * p , unsigned * u ) { *u = 0 ; for (int i = 0 ; i < 4 ; i++) { char ch = *p++; *u <<= 4 ; if (ch >= '0' && ch <= '9' ) *u |= ch - '0'; else if (ch >= 'A ' && ch <= 'F ') * u |= ch - ('A ' - 10); else if (ch >= 'a' && ch <= 'f') * u |= ch - ('a' - 10); else return NULL ; } return p; } static void lept_encode_utf8(lept_context * c , unsigned u ) { / * \TODO * / if (u <= 0x7F ) PUTC(c , u & 0xFF) ; else if (u <= 0x7FF ) { PUTC(c , 0xC0 | ((u >> 6) & 0xFF )); PUTC(c , 0x80 | (u & 0x3F) ); } else if (u <= 0xFFFF ) { PUTC(c , 0xE0 | ((u >> 12) & 0xFF )); PUTC(c , 0x80 | ((u >> 6) & 0x3F )); PUTC(c , 0x80 | (u & 0x3F) ); } else { assert (u <= 0x10FFFF ); PUTC(c , 0xF0 | ((u >> 18) & 0xFF )); PUTC(c , 0x80 | ((u >> 12) & 0x3F )); PUTC(c , 0x80 | ((u >> 6) & 0x3F )); PUTC(c , 0x80 | (u & 0x3F) ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 case 'u' : if (!(p = lept_parse_hex4(p , &u ) )) STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX) ; if (u >= 0xD800 && u <= 0xDC00 ) { if (*p++!='\\' ) STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE) ; if (*p++!='u' ) STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE) ; unsigned u2=0 ; if (!(p=lept_parse_hex4(p ,&u2 ) )) STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE) ; if (u2 < 0xDC00 || u2 > 0xDFFF ) STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE) ; u = (((u - 0xD800 ) << 10 ) | (u2 - 0xDC00 )) + 0x10000; } lept_encode_utf8(c , u ) ; break;
05 解析数组 json 的数组语法是这样的
1 array = %x5B ws [ value *( ws %x2C ws value ) ] ws %x5D
可以像字符串一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 static int lept_parse_array(lept_context * c , lept_value * v ) { size_t i, size = 0 ; int ret; EXPECT(c , '[') ; lept_parse_whitespace(c ) ; if (*c->json == ']' ) { c->json++; v->type = LEPT_ARRAY; v->u.a.size = 0 ; v->u.a.e = NULL; return LEPT_PARSE_OK; } for (;;) { lept_value e; lept_init(&e ) ; if ((ret = lept_parse_value(c , &e ) ) != LEPT_PARSE_OK) break; memcpy(lept_context_push(c , sizeof (lept_value ) ), &e, sizeof(lept_value)); size++; lept_parse_whitespace(c ) ; if (*c->json == ',' ) { c->json++; lept_parse_whitespace(c ) ; } else if (*c->json == ']' ) { c->json++; v->type = LEPT_ARRAY; v->u.a.size = size; size *= sizeof(lept_value); memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c , size ) , size); return LEPT_PARSE_OK; } else { ret = LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET; break; } } for (i = 0 ; i < size; i++) lept_free((lept_value * ) lept_context_pop(c , sizeof (lept_value ) )); return ret; }
防止内存泄漏 更新lept_free 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void lept_free(lept_value* v) { size_t i; assert(v != NULL ); switch (v->type) { case LEPT_STRING: free(v->u.s.s); break ; case LEPT_ARRAY: for (i = 0 ; i < v->u.a.size; i++) lept_free(&v->u.a.e[i]); free(v->u.a.e); break ; default : break ; } v->type = LEPT_NULL; }
BUG 然后,我们把这个指针调用 lept_parse_value(c, e)
,这里会出现问题,因为 lept_parse_value()
及之下的函数都需要调用 lept_context_push()
,而 lept_context_push()
在发现栈满了的时候会用 realloc()
扩容。这时候,我们上层的 e
就会失效,变成一个悬挂指针(dangling pointer),而且 lept_parse_value(c, e)
会通过这个指针写入解析结果,造成非法访问。
在使用 C++ 容器时,也会遇到类似的问题。从容器中取得的迭代器(iterator)后,如果改动容器内容,之前的迭代器会失效。这里的悬挂指针问题也是相同的。
但这种 bug 有时可能在简单测试中不能自动发现,因为问题只有堆栈满了才会出现。从测试的角度看,我们需要一些压力测试(stress test),测试更大更复杂的数据。但从编程的角度看,我们要谨慎考虑变量的生命周期,尽量从编程阶段避免出现问题。例如把 lept_context_push()
的 API 改为:
1 static void lept_context_push (lept_context* c, const void * data, size_t size) ;
这样就确把数据压入栈内,避免了返回指针的生命周期问题。但我们之后会发现,原来的 API 设计在一些情况会更方便一些,例如在把字符串值转化(stringify)为 JSON 时,我们可以预先在堆栈分配字符串所需的最大空间,而当时是未有数据填充进去的。
无论如何,我们编程时都要考虑清楚变量的生命周期,特别是指针的生命周期
06 JSON对象 1 2 member = string ws %x3A ws value object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D
数据结构
动态数组
有序动态数组
平衡树
哈希表
有序
否
是
是
否
自定成员次序
可
否
否
否
初始化 n 个成员
O(n)
O(n log n)
O(n log n)
平均 O(n)、最坏 O(n^2)
加入成员
分摊 O(1)
O(n)
O(log n)
平均 O(1)、最坏 O(n)
移除成员
O(n)
O(n)
O(log n)
平均 O(1)、最坏 O(n)
查询成员
O(n)
O(log n)
O(log n)
平均 O(1)、最坏 O(n)
遍历成员
O(n)
O(n)
O(n)
O(m)
检测对象相等
O(n^2)
O(n)
O(n)
平均 O(n)、最坏 O(n^2)
空间
O(m)
O(m)
O(n)
O(m)
创建动态数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 typedef struct lept_value lept_value ;typedef struct lept_member lept_member ;struct lept_value { union { struct { lept_member* m; size_t size; }o; struct { lept_value* e; size_t size; }a; struct { char * s; size_t len; }s; double n; }u; lept_type type; };struct lept_member { char * k; size_t klen; lept_value v; };
重构字符串解析 把解析 JSON 字符串及写入 lept_value
分拆成两部分
重构后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 static int lept_parse_string_raw (lept_context* c, char ** str, size_t * len) { size_t head = c->top; unsigned u, u2; const char * p; EXPECT (c, '\"' ); p = c->json; for (;;) { char ch = *p++; switch (ch) { case '\"' : *len = c->top - head; *str = lept_context_pop (c, *len); c->json = p; return LEPT_PARSE_OK; case '\\' : switch (*p++) { case '\"' : PUTC (c, '\"' ); break ; case '\\' : PUTC (c, '\\' ); break ; case '/' : PUTC (c, '/' ); break ; case 'b' : PUTC (c, '\b' ); break ; case 'f' : PUTC (c, '\f' ); break ; case 'n' : PUTC (c, '\n' ); break ; case 'r' : PUTC (c, '\r' ); break ; case 't' : PUTC (c, '\t' ); break ; case 'u' : if (!(p = lept_parse_hex4 (p, &u))) STRING_ERROR (LEPT_PARSE_INVALID_UNICODE_HEX); if (u >= 0xD800 && u <= 0xDBFF ) { if (*p++ != '\\' ) STRING_ERROR (LEPT_PARSE_INVALID_UNICODE_SURROGATE); if (*p++ != 'u' ) STRING_ERROR (LEPT_PARSE_INVALID_UNICODE_SURROGATE); if (!(p = lept_parse_hex4 (p, &u2))) STRING_ERROR (LEPT_PARSE_INVALID_UNICODE_HEX); if (u2 < 0xDC00 || u2 > 0xDFFF ) STRING_ERROR (LEPT_PARSE_INVALID_UNICODE_SURROGATE); u = (((u - 0xD800 ) << 10 ) | (u2 - 0xDC00 )) + 0x10000 ; } lept_encode_utf8 (c, u); break ; default : STRING_ERROR (LEPT_PARSE_INVALID_STRING_ESCAPE); } break ; case '\0' : STRING_ERROR (LEPT_PARSE_MISS_QUOTATION_MARK); default : if ((unsigned char )ch < 0x20 ) STRING_ERROR (LEPT_PARSE_INVALID_STRING_CHAR); PUTC (c, ch); } } }static int lept_parse_string (lept_context* c, lept_value* v) { int ret; char * s; size_t len; if ((ret = lept_parse_string_raw (c, &s, &len)) == LEPT_PARSE_OK) lept_set_string (v, s, len); return ret; }
解析对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 static int lept_parse_object(lept_context * c , lept_value * v ) { size_t i,size; lept_member m; int ret; EXPECT(c , '{') ; lept_parse_whitespace(c ) ; if (*c->json == '}' ) { c->json++; v->type = LEPT_OBJECT; v->u.o.m = 0 ; v->u.o.size = 0 ; return LEPT_PARSE_OK; } m.k = NULL; size = 0 ; for (;;) { char * str; lept_init(&m .v ) ; if (*c->json != '"' ) { ret = LEPT_PARSE_MISS_KEY; break; } if ((ret = lept_parse_string_raw(c , &str , &m .klen ) ) != LEPT_PARSE_OK) break; memcpy(m.k = (char *)malloc(m.klen + 1 ), str, m.klen); m.k[m .klen ] = '\0' ; lept_parse_whitespace(c ) ; if (*c->json != ':' ) { ret = LEPT_PARSE_MISS_COLON; break; } c->json++; lept_parse_whitespace(c ) ; if ((ret = lept_parse_value(c , &m .v ) ) != LEPT_PARSE_OK) break; memcpy(lept_context_push(c , sizeof (lept_member ) ), &m, sizeof(lept_member)); size++; m.k = NULL; lept_parse_whitespace(c ) ; if (*c->json == ',' ) { c->json++; lept_parse_whitespace(c ) ; } else if (*c->json == '}' ) { size_t s = sizeof(lept_member) * size; c->json++; v->type = LEPT_OBJECT; v->u.o.size = size; memcpy(v->u.o.m = (lept_member*)malloc(s), lept_context_pop(c , s ) , s); return LEPT_PARSE_OK; } else { ret= LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET; break; } } free(m.k); for (i = 0 ; i < size; i++) { lept_member* m = (lept_member*)lept_context_pop(c , sizeof (lept_member ) ); free(m->k); lept_free(&m ->v ) ; } return ret; }
到此基本完成了所有的解析器
07 JSON 生成器 字符串化 我们在前 6 个单元实现了一个合乎标准的 JSON 解析器,它把 JSON 文本解析成一个树形数据结构,整个结构以 lept_value
的节点组成。
JSON 生成器(generator)负责相反的事情,就是把树形数据结构转换成 JSON 文本。这个过程又称为「字符串化(stringify)」。
相对于解析器,通常生成器更容易实现,而且生成器几乎不会造成运行时错误。因此,生成器的 API 设计为以下形式,直接返回 JSON 的字符串:
lept_context 动态数组 再利用 在实现 JSON 解析时,我们加入了一个动态变长的堆栈,用于存储临时的解析结果。而现在,我们也需要存储生成的结果,所以最简单是再利用该数据结构,作为输出缓冲区。
1 2 3 4 5 6 7 8 9 10 11 12 char * lept_stringify(const lept_value * v , size_t * length ) { lept_context c; assert (v != NULL); c.stack = (char *)malloc(c.size = LEPT_PARSE_STRINGIFY_INIT_SIZE); c.top = 0 ; lept_stringify_value(&c , v ) ; if (length) *length = c.top; PUTC(&c , '\0') ; return c.stack; }
字符串 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 static void lept_stringify_string (lept_context* c, const char * s, size_t len) { static const char hex_digits[] = { '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'A' , 'B' , 'C' , 'D' , 'E' , 'F' }; size_t i, size; char * head, *p; assert (s != NULL ); p = head = lept_context_push (c, size = len * 6 + 2 ); *p++ = '"' ; for (i = 0 ; i < len; i++) { unsigned char ch = (unsigned char )s[i]; switch (ch) { case '\"' : *p++ = '\\' ; *p++ = '\"' ; break ; case '\\' : *p++ = '\\' ; *p++ = '\\' ; break ; case '\b' : *p++ = '\\' ; *p++ = 'b' ; break ; case '\f' : *p++ = '\\' ; *p++ = 'f' ; break ; case '\n' : *p++ = '\\' ; *p++ = 'n' ; break ; case '\r' : *p++ = '\\' ; *p++ = 'r' ; break ; case '\t' : *p++ = '\\' ; *p++ = 't' ; break ; default : if (ch < 0x20 ) { *p++ = '\\' ; *p++ = 'u' ; *p++ = '0' ; *p++ = '0' ; *p++ = hex_digits[ch >> 4 ]; *p++ = hex_digits[ch & 15 ]; } else *p++ = s[i]; } } *p++ = '"' ; c->top -= size - (p - head); }
数字 为了简单起见,我们使用 sprintf("%.17g", ...)
来把浮点数转换成文本。"%.17g"
是足够把双精度浮点转换成可还原的文本。
最简单的实现方式可能是这样的:
1 2 3 4 5 6 7 case LEPT_NUMBER: { char buffer[32 ]; int length = sprintf (buffer, "%.17g" , v->u.n); PUTS(c, buffer, length); } break ;
但这样需要在 PUTS()
中做一次 memcpy()
,实际上我们可以避免这次复制,只需要生成的时候直接写进 c
里的堆栈,然后再按实际长度调查 c->top
:
1 2 3 4 5 6 7 case LEPT_NUMBER: { char * buffer = lept_context_push(c, 32 ); int length = sprintf (buffer, "%.17g" , v->u.n); c->top -= 32 - length; } break ;
因每个临时变量只用了一次,我们可以把代码压缩成一行:
1 2 3 case LEPT_NUMBER: c->top -= 32 - sprintf (lept_context_push(c, 32 ), "%.17g" , v->u.n); break ;
08 元素比较 在实现数组和对象的修改之前,为了测试结果的正确性,我们先实现 lept_value
的相等比较 (equality comparison)。首先,两个值的类型必须相同,对于 true、false、null 这三种类型,比较类型后便完成比较。而对于数字和字符串,需进一步检查是否相等:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 int lept_is_equal(const lept_value * lhs , const lept_value * rhs ) { size_t i; assert (lhs != NULL && rhs != NULL); if (lhs->type != rhs->type ) return 0 ; switch (lhs->type ) { case LEPT_STRING: return lhs->u.s.len == rhs->u.s.len && memcmp(lhs->u.s.s, rhs->u.s.s, lhs->u.s.len) == 0 ; case LEPT_NUMBER: return lhs->u.n == rhs->u.n; case LEPT_ARRAY: if (lhs->u.a.size != rhs->u.a.size) return 0 ; for (i = 0 ; i < lhs->u.a.size; i++) if (!lept_is_equal(&lhs ->u .a .e [i ], &rhs ->u .a .e [i ]) ) return 0 ; return 1 ; case LEPT_OBJECT: if (lhs->u.o.size != rhs->u.o.size) return 0 ; for (i = 0 ; i < lhs->u.o.size; i++) { size_t idx; if ((idx = lept_find_object_index(rhs , lhs ->u .o .m [i ].k , lhs ->u .o .m [i ].klen ) ) == LEPT_KEY_NOT_EXIST) return 0 ; if (!lept_is_equal(&lhs ->u .o .m [i ].v , &rhs ->u .o .m [idx ].v ) ) return 0 ; } return 1 ; default: return 1 ; } }
而对象与数组的不同之处,在于概念上对象的键值对是无序的。例如,{"a":1,"b":2}
和 {"b":2,"a":1}
虽然键值的次序不同,但这两个 JSON 对象是相等的。我们可以简单地利用 lept_find_object_index()
去找出对应的值,然后递归作比较。
lept_pushback_array_element() 1 2 3 4 5 6 7 8 9 10 11 12 lept_value* lept_pushback_array_element(lept_value * v ) { assert (v != NULL && v->type == LEPT_ARRAY); if (v->u.a.size == v->u.a.capacity) lept_reserve_array(v , v ->u .a .capacity == 0 ? 1 : v ->u .a .capacity * 2) ; lept_init(&v ->u .a .e [v ->u .a .size ]) ; return &v->u.a.e[v ->u .a .size ++ ] ; } void lept_popback_array_element(lept_value * v ) { assert (v != NULL && v->type == LEPT_ARRAY && v->u.a.size > 0 ); lept_free(&v ->u .a .e [--v ->u .a .size ]) ; }
lept_pushback_array_element()
在数组末端压入一个元素,返回新的元素指针。如果现有的容量不足,就需要调用 lept_reserve_array()
扩容。我们现在用了一个最简单的扩容公式:若容量为 0,则分配 1 个元素;其他情况倍增容量。
lept_popback_array_element()
则做相反的工作,记得删去的元素需要调用 lept_free()
。
lept_insert_array_element() 1 2 3 4 5 6 7 8 9 10 11 12 lept_value* lept_insert_array_element(lept_value* v, size_t index) { assert (v != NULL && v->type == LEPT_ARRAY && index <= v-> u.a.size); if (v->u .a.size >= v-> u.a.capacity) { lept_reserve_array (v, v->u .a.capacity == 0 ? 1 : v-> u.a.capacity * 2 ); } memcpy (v->u .a.e + index + 1, v->u .a.e + index, (v-> u.a.size - index) * sizeof(lept_value)); lept_init (&v-> u.a.e[index]); v -> u.a.size++; return v-> u.a.e+index; }
这个函数的作用是在指定的index位置腾出一个位置。 首先检查size和capacity,看是否要扩容。接着将index位置开始的元素整体向后移动一位。 最后使用lept_init()
将index位置的元素初始化,同时size++,返回地址。
lept_erase_array_element() 1 2 3 4 5 6 7 8 9 10 11 void lept_erase_array_element(lept_value * v , size_t index , size_t count ) { assert (v != NULL && v->type == LEPT_ARRAY && index + count <= v->u.a.size); size_t i; for (int i = index; i < count + index; i++) lept_init(&v ->u .a .e [i ]) ; memcpy(v->u.a.e + index, v->u.a.e + index + count, (v->u.a.size - index - count) * sizeof(lept_value)); for (int i = v->u.a.size - count; i < v->u.a.size; i++) lept_init(&v ->u .a .e [i ]) ; v->u.a.size -= count; }
这个函数的作用是删除从某个位置开始的,指定数目的元素 所以首先lept_free()
释放内存,接着将数组后面剩下的元素往前移动
因为这样数组少了count
个元素,所以最末尾的count
元素是无效值,调用lept_init()
初始化 最后更新size
动态对象 object 相关代码补全 lept_reserve_object() 1 2 3 4 5 6 7 8 9 void lept_reserve_object(lept_value* v, size_t capacity) { assert (v != NULL && v-> type == LEPT_OBJECT); if (v-> u.o.capacity < capacity) { v -> u.o.capacity = capacity; v ->u .o.m = (lept_member*)realloc(v-> u.o.m, capacity * sizeof(lept_member)); } }
参考 lept_reserve_array 即可
lept_get_object_capacity() 1 2 3 4 5 size_t lept_get_object_capacity(const lept_value * v ) { assert (v != NULL && v->type == LEPT_OBJECT); return v->u.o.capacity; }
lept_set_object() 1 2 3 4 5 6 7 8 void lept_set_object(lept_value * v , size_t capacity ) { assert (v != NULL); lept_free(v ) ; v->type = LEPT_OBJECT; v->u.o.size = 0 ; v->u.o.capacity = capacity; v->u.o.m = capacity > 0 ? (lept_member*)malloc(capacity * sizeof(lept_member)) : NULL; }
释放原来的内存,初始化类型和容量,分配新的内存
lept_shrink_object() 1 2 3 4 5 6 7 8 void lept_shrink_object(lept_value* v) { assert (v != NULL && v-> type == LEPT_OBJECT); if (v->u .o.capacity > v-> u.o.size) { v ->u .o.capacity = v-> u.o.size; v ->u .o.m = (lept_member*)realloc(v->u .o.m, v-> u.o.capacity * sizeof(lept_member)); } }
重新分配内存
lept_clear_object() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 void lept_clear_object(lept_value * v ) { assert (v != NULL && v->type == LEPT_OBJECT); size_t i; for (int i = 0 ; i < v->u.o.size; i++) { char * k = v->u.o.m[i ] .k; free(k); v->u.o.m[i ] .klen = 0 ; k = NULL; lept_free(&v ->u .o .m [i ].v ) ; } v->u.o.size = 0 ; }
清除所有内存
依次free lept_member里面的 key klen value
lept_set_object_value() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 lept_value* lept_set_object_value(lept_value * v , const char * key , size_t klen ) { assert (v != NULL && v->type == LEPT_OBJECT && key != NULL); size_t index=lept_find_object_index(v ,key ,klen ) ; if (index != LEPT_KEY_NOT_EXIST) return &v->u.o.m[index ] .v; if (v->u.o.size == v->u.o.capacity) lept_reserve_object(v , v ->u .o .capacity == 0 ? 1 : v ->u .o .capacity * 2) ; size_t sz = v->u.o.size; v->u.o.m[sz ] .k = (char *)malloc(klen + 1 ); memcpy(v->u.o.m[sz ] .k, key, klen); v->u.o.m[sz ] .k[klen ] = "\0" ; v->u.o.m[sz ] .klen = klen; lept_init(&v ->u .o .m [sz ].v ) ; return &v->u.o.m[v ->u .o .size ++ ] .v; }
根据key返回对应的value,如果不存在,则新建一个键值对,key通过参数深复制达成同步
注意 lept_set_object_value()
会先搜寻是否存在现有的键,若存在则直接返回该值的指针,不存在时才新增
lept_remove_object_value() 1 2 3 4 5 6 7 8 9 10 11 12 void lept_remove_object_value(lept_value* v, size_t index) { assert (v != NULL && v->type == LEPT_OBJECT && index < v-> u.o.size); lept_member * rm = &v-> u.o.m[index]; free (rm-> k); lept_free (&rm-> v); memcpy (v->u .o.m + index, v->u .o.m + index + 1, (v-> u.o.size - index - 1 ) * sizeof(lept_member)); lept_member * ed = &v->u .o.m[--v-> u.o.size]; ed -> k = NULL; ed -> klen = 0 ; lept_init (&ed-> v); }
首先释放原先的键值对(lept_member
),将后面剩下的元素整体往前移动一位。 不要忘记对最后结尾的元素初始化,因为它们已经往前移动,剩下的是无效值。
元素移动,复制 深度复制 例子
1 2 3 4 5 6 7 8 9 10 11 12 void lept_set_object_value(lept_value * v , const char * key , size_t klen , const lept_value * value ) ; void f() { lept_value v, s; lept_init(&v ) ; lept_parse(&v , "{}" ) ; lept_init(&s ) ; lept_set_string(&s , "Hello" , 5) ; lept_set_object_keyvalue(&v , "s" , &s ) ; lept_free(&v ) lept_free(&s ) ; }
凡涉及赋值,都可能会引起资源拥有权(resource ownership)的问题。值 s
并不能以指针方式简单地写入对象 v
,因为这样便会有两个地方都拥有 s
,会做成重复释放的 bug。我们有两个选择:
在 lept_set_object_value()
中,把参数 value
深度复制 (deep copy)一个值,即把整个树复制一份,写入其新增的键值对中。
在 lept_set_object_value()
中,把参数 value
拥有权转移至新增的键值对,再把 value
设置成 null 值。这就是所谓的移动语意(move semantics)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 void lept_copy(lept_value * dst , const lept_value * src ) { size_t sz,i; assert (src != NULL && dst != NULL && src != dst); switch (src->type ) { case LEPT_STRING: lept_set_string(dst , src ->u .s .s , src ->u .s .len ) ; break; case LEPT_ARRAY: sz = src->u.a.size; lept_set_array(dst , sz ) ; for (int i = 0 ; i < sz; i++) { lept_copy(&dst ->u .a .e [i ],&src ->u .a .e [i ]) ; } dst->u.a.size = sz; break; case LEPT_OBJECT: lept_set_object(dst , src ->u .o .size ) ; for (i = 0 ; i < src->u.o.size; ++i) { lept_member* src_member = &src->u.o.m[i ] ; lept_value* dst_value = lept_set_object_value(dst , src_member ->k , src_member ->klen ) ; lept_copy(dst_value , &src_member ->v ) ; } break; default: lept_free(dst ) ; memcpy(dst, src, sizeof(lept_value)); break; } }
C++11 加入了右值引用的功能,可以从语言层面区分复制和移动语意。而在 C 语言中,我们也可以通过实现不同版本的接口(不同名字的函数),实现这两种语意。但为了令接口更简单和正交(orthogonal),我们修改了 lept_set_object_value()
的设计,让它返回新增键值对的值指针,所以我们可以用 lept_copy()
去复制赋值,也可以简单地改变新增的键值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 lept_value * lept_set_object_value (lept_value* v, const char* key, size_t klen);void f () { lept_value v ; lept_init (&v); lept_parse (&v, "{}" ); lept_set_string (lept_set_object_value(&v, "s" ), "Hello" , 5 ); lept_copy ( lept_add_object_keyvalue(&v, "t" ), lept_get_object_keyvalue(&v, "s" , 1 )); lept_free (&v); }
移动 1 2 3 4 5 6 void lept_move(lept_value * dst , lept_value * src ) { assert (dst != NULL && src != NULL && src != dst); lept_free(dst ) ; memcpy(dst, src, sizeof(lept_value)); lept_init(src ) ; }
类似,交换值同理
1 2 3 4 5 6 7 8 9 void lept_swap (lept_value* lhs, lept_value* rhs) { assert (lhs != NULL && rhs != NULL ); if (lhs != rhs) { lept_value temp; memcpy (&temp, lhs, sizeof (lept_value)); memcpy (lhs, rhs, sizeof (lept_value)); memcpy (rhs, &temp, sizeof (lept_value)); } }
例子 当我们要修改对象或数组里的值时,我们可以利用这 3 个函数。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 const char char lept_value v; lept_init(&v ); lept_parse(&v , json); lept_copy( lept_find_object_value(&v , "b" , 1), lept_find_object_value(&v , "a" , 1)); printf("%s\n" , out = lept_stringify(&v , NULL )); free(out ); lept_parse(&v , json); lept_move( lept_find_object_value(&v , "b" , 1), lept_find_object_value(&v , "a" , 1)); printf("%s\n" , out = lept_stringify(&v , NULL )); free(out ); lept_parse(&v , json); lept_swap( lept_find_object_value(&v , "b" , 1), lept_find_object_value(&v , "a" , 1)); printf("%s\n" , out = lept_stringify(&v , NULL )); free(out ); lept_free(&v );