1、JSON的数字语法
本单元的重点在于解析 JSON number 类型。我们先看看它的语法:
number = [ "-" ] int [ frac ] [ exp ]
int = "0" / digit1-9 *digit
frac = "." 1*digit
exp = ("e" / "E") ["-" / "+"] 1*digit
number 是以十进制表示,它主要由 4 部分顺序组成:负号、整数、小数、指数。只有整数是必需部分。注意和直觉可能不同的是,正号是不合法的。
整数部分如果是 0 开始,只能是单个 0;而由 1-9 开始的话,可以加任意数量的数字(0-9)。也就是说,0123
不是一个合法的 JSON 数字。
小数部分比较直观,就是小数点后是一或多个数字(0-9)。
JSON 可使用科学记数法,指数部分由大写 E 或小写 e 开始,然后可有正负号,之后是一或多个数字(0-9)。
2、数字表示方式
从 JSON 数字的语法,我们可能直观地会认为它应该表示为一个浮点数(floating point number),因为它带有小数和指数部分。然而,标准中并没有限制数字的范围或精度。为简单起见,leptjson 选择以双精度浮点数(C 中的 double
类型)来存储 JSON 数字。
我们为 lept_value
添加成员:
leptjson.h
#ifndef LEPTJSON_H__
#define LEPTJSON_H__
/*防卫式声明*/
/*JSON 中有 7 种数据类型,用枚举类型定义,类型别名为 lept_type */
typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type;
/*声明 JSON 的数据结构。JSON 是一个树形结构,我们最终需要实现一个树的数据结构,
每个节点使用 lept_value 结构体表示,我们会称它为一个 JSON 值(JSON value)。
在此单元中,我们只需要实现 数字 的解析,
因此该结构体不仅存储 lept_type,还需要有具体的数据n
类型别名是 lept_value
*/
typedef struct {
double n;
lept_type type;
}lept_value;
/*下面解析函数的返回类型*/
enum {
LEPT_PARSE_OK = 0, /*OK*/
LEPT_PARSE_EXPECT_VALUE, /*解析期望值*/
LEPT_PARSE_INVALID_VALUE, /*解析非法数据*/
LEPT_PARSE_ROOT_NOT_SINGULAR, /*解析根不是单数,根就是双数*/
LEPT_PARSE_NUMBER_TOO_BIG
};
/*解析 JSON : 将JSON文本解析成树状数据结构
lept_value* v : 根结点
const char* json : JSON 的 C 字符串
*/
int lept_parse(lept_value* v, const char* json);
/*访问结果的函数,就是获取其类型*/
lept_type lept_get_type(const lept_value* v);
/*获取具体的数据*/
double lept_get_number(const lept_value* v);
#endif /* LEPTJSON_H__ */
仅当 type == LEPT_NUMBER
时,n
才表示 JSON 数字的数值。所以获取该值的 API 是这么实现的:
double lept_get_number(const lept_value* v)
{
assert(v != NULL && v->type == LEPT_NUMBER);
return v->n;
}
使用者应确保类型正确,才调用此 API。我们继续使用断言来保证。
3、单元测试
test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "leptjson.h"
static int main_ret = 0; /*main函数的返回值*/
static int test_count = 0; /*测试计数*/
static int test_pass = 0; /*测试通过计数*/
/*反斜线代表该行未结束,会串接下一行*/
/*这是一个宏:EXPECT_EQ_BASE(equality, expect, actual, format)
参数:equality(相等),expect(期望),actual(实际),format(格式)
*/
#define EXPECT_EQ_BASE(equality, expect, actual, format) \
/*
大框架是一个do{}while
先测试计数自加
再判断相等是否为真,为真:测试通过自加;为假:输出相关信息。并且将main函数的返回值置为1
*/
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")
/*判断浮点数*/
#define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g")
/*测试 "null"*/
static void test_parse_null() {
lept_value v;
v.type = LEPT_FALSE;
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null"));
EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));
}
/*测试 "true"*/
static void test_parse_true() {
lept_value v;
v.type = LEPT_FALSE;
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true"));
EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v));
}
/*测试 "false"*/
static void test_parse_false() {
lept_value v;
v.type = LEPT_TRUE;
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false"));
EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v));
}
/*测试expect和json。【这是本次的重点】*/
#define TEST_NUMBER(expect, json)\
do {\
/*定义变量 v*/
lept_value v;\
/*判断 v这个JSON数据 和 LEPT_PARSE_OK 是否相等*/
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\
/*判断v的数据类型是否是LEPT_NUMBER */
EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(&v));\
/*判读v是否等于expect*/
EXPECT_EQ_DOUBLE(expect, lept_get_number(&v));\
} while(0)
/*测试数据*/
static void test_parse_number() {
TEST_NUMBER(0.0, "0");
TEST_NUMBER(0.0, "-0");
TEST_NUMBER(0.0, "-0.0");
TEST_NUMBER(1.0, "1");
TEST_NUMBER(-1.0, "-1");
TEST_NUMBER(1.5, "1.5");
TEST_NUMBER(-1.5, "-1.5");
TEST_NUMBER(3.1416, "3.1416");
TEST_NUMBER(1E10, "1E10");
TEST_NUMBER(1e10, "1e10");
TEST_NUMBER(1E+10, "1E+10");
TEST_NUMBER(1E-10, "1E-10");
TEST_NUMBER(-1E10, "-1E10");
TEST_NUMBER(-1e10, "-1e10");
TEST_NUMBER(-1E+10, "-1E+10");
TEST_NUMBER(-1E-10, "-1E-10");
TEST_NUMBER(1.234E+10, "1.234E+10");
TEST_NUMBER(1.234E-10, "1.234E-10");
TEST_NUMBER(0.0, "1e-10000"); /* must underflow */
}
/*测试error和json*/
#define TEST_ERROR(error, json)\
do {\
/*定义变量 v*/
lept_value v;\
/*将v的数据类型修改为 LEPT_FALSE*/
v.type = LEPT_FALSE;\
/*判断error 和v是否相等*/
EXPECT_EQ_INT(error, lept_parse(&v, json));\
/*判断v的数据类型是否是 LEPT_NULL*/
EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\
} while(0)
/*测试解析期望值*/
static void test_parse_expect_value() {
/*测试LEPT_PARSE_EXPECT_VALUE 和"" 是否相等*/
TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, "");
/*测试LEPT_PARSE_EXPECT_VALUE 和" " 是否相等*/
TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " ");
}
/*解析非法的数据*/
static void test_parse_invalid_value() {
/* 解析"nul"是否是非法的数据 */
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul");
/* 解析"?"是否是非法的数据 */
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?");
/*这些都是非法的数据*/
#if 0
/* invalid number */
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0");
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1");
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF");
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf");
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN");
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan");
#endif
}
/*解析根不是单数,即根是双数*/
static void test_parse_root_not_singular() {
TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x");
/*非法的数据。0之后是'.'或者'E'或者'e'*/
#if 0
/* invalid number */
TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' , 'E' , 'e' or nothing */
TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0");
TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123");
#endif
}
/*解析大数据*/
static void test_parse_number_too_big() {
#if 0
TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309");
TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309");
#endif
}
static void test_parse() {
test_parse_null();
test_parse_true();
test_parse_false();
test_parse_number();
test_parse_expect_value();
test_parse_invalid_value();
test_parse_root_not_singular();
test_parse_number_too_big();
}
int main() {
test_parse();
printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count);
return main_ret;
}
写一些单元测试。这次我们使用多行的宏的减少重复代码:
【从test.c中摘录一部分】
#define TEST_NUMBER(expect, json)\
do {\
lept_value v;\
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\
EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(&v));\
EXPECT_EQ_DOUBLE(expect, lept_get_number(&v));\
} while(0)
static void test_parse_number() {
TEST_NUMBER(0.0, "0");
TEST_NUMBER(0.0, "-0");
TEST_NUMBER(0.0, "-0.0");
TEST_NUMBER(1.0, "1");
TEST_NUMBER(-1.0, "-1");
TEST_NUMBER(1.5, "1.5");
TEST_NUMBER(-1.5, "-1.5");
TEST_NUMBER(3.1416, "3.1416");
TEST_NUMBER(1E10, "1E10");
TEST_NUMBER(1e10, "1e10");
TEST_NUMBER(1E+10, "1E+10");
TEST_NUMBER(1E-10, "1E-10");
TEST_NUMBER(-1E10, "-1E10");
TEST_NUMBER(-1e10, "-1e10");
TEST_NUMBER(-1E+10, "-1E+10");
TEST_NUMBER(-1E-10, "-1E-10");
TEST_NUMBER(1.234E+10, "1.234E+10");
TEST_NUMBER(1.234E-10, "1.234E-10");
TEST_NUMBER(0.0, "1e-10000"); /* must underflow */
}
以上这些都是很基本的测试用例,也可供调试用。大部分情况下,测试案例不能穷举所有可能性。因此,除了加入一些典型的用例,我们也常会使用一些边界值,例如最大值等。练习中会让同学找一些边界值作为用例。
除了这些合法的 JSON,我们也要写一些不合语法的用例:
static void test_parse_invalid_value() {
/* ... */
/* invalid number */
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0");
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1");
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF");
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf");
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN");
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan");
}
4、十进制转换至二进制
leptjson.c
#include "leptjson.h"
#include <assert.h> /* assert() */
#include <stdlib.h> /* NULL, strtod() */
/*判断指针c的第一个位置是否是 ch,并且移动指针*/
#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0)
/*封装JSON的结构体*/
typedef struct {
const char* json;
}lept_context;
/* 轻量解析空白,空白有4中类型
空格、制表符、换行符、回车符
ws = *(%x20 / %x09 / %x0A / %x0D) */
static void lept_parse_whitespace(lept_context* c) {
const char *p = c->json;
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')
p++;
c->json = p;
}
/*解析"true"*/
static int lept_parse_true(lept_context* c, lept_value* v) {
EXPECT(c, 't');
if (c->json[0] != 'r' || c->json[1] != 'u' || c->json[2] != 'e')
return LEPT_PARSE_INVALID_VALUE;
c->json += 3;
v->type = LEPT_TRUE;
return LEPT_PARSE_OK;
}
/*解析"false"*/
static int lept_parse_false(lept_context* c, lept_value* v) {
EXPECT(c, 'f');
if (c->json[0] != 'a' || c->json[1] != 'l' || c->json[2] != 's' || c->json[3] != 'e')
return LEPT_PARSE_INVALID_VALUE;
c->json += 4;
v->type = LEPT_FALSE;
return LEPT_PARSE_OK;
}
/*解析"null"*/
static int lept_parse_null(lept_context* c, lept_value* v) {
EXPECT(c, 'n');
if (c->json[0] != 'u' || c->json[1] != 'l' || c->json[2] != 'l')
return LEPT_PARSE_INVALID_VALUE;
c->json += 3;
v->type = LEPT_NULL;
return LEPT_PARSE_OK;
}
/*解析 数字 【这是本次的重点】*/
static int lept_parse_number(lept_context* c, lept_value* v) {
char* end;
/* \TODO validate number */
v->n = strtod(c->json, &end);
if (c->json == end)
return LEPT_PARSE_INVALID_VALUE;
c->json = end;
v->type = LEPT_NUMBER;
return LEPT_PARSE_OK;
}
/*
轻量解析数据函数
传入:封装的JSON数据结构体 和 JSON数据结构
*/
static int lept_parse_value(lept_context* c, lept_value* v) {
switch (*c->json) {
case 't': return lept_parse_true(c, v);
case 'f': return lept_parse_false(c, v);
case 'n': return lept_parse_null(c, v);
default: return lept_parse_number(c, v);
case '\0': return LEPT_PARSE_EXPECT_VALUE;
}
}
/*修正关于 LEPT_PARSE_ROOT_NOT_SINGULAR 的单元测试,若 json 在一个值之后,空白之后还有其它字符,则要返回 LEPT_PARSE_ROOT_NOT_SINGULAR*/
int lept_parse(lept_value* v, const char* json) {
lept_context c;
int ret;
assert(v != NULL);
c.json = json;
v->type = LEPT_NULL;
lept_parse_whitespace(&c);
if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) {
lept_parse_whitespace(&c);
if (*c.json != '\0') {
v->type = LEPT_NULL;
ret = LEPT_PARSE_ROOT_NOT_SINGULAR;
}
}
return ret;
}
/*获取类型 函数的实现*/
lept_type lept_get_type(const lept_value* v) {
assert(v != NULL);
return v->type;
}
/*仅当 `type == LEPT_NUMBER` 时,`n` 才表示 JSON 数字的数值*/
double lept_get_number(const lept_value* v) {
assert(v != NULL && v->type == LEPT_NUMBER);
return v->n;
}
我们需要把十进制的数字转换成二进制的 double
。这并不是容易的事情 。为了简单起见,leptjson 将使用标准库的 strtod() 来进行转换。strtod()
可转换 JSON 所要求的格式,但问题是,一些 JSON 不容许的格式,strtod()
也可转换,所以我们需要自行做格式校验。
#include <stdlib.h> /* NULL, strtod() */
static int lept_parse_number(lept_context* c, lept_value* v) {
char* end;
/* \TODO validate number */
v->n = strtod(c->json, &end);
if (c->json == end)
return LEPT_PARSE_INVALID_VALUE; /*返回解析非法数据*/
c->json = end;
v->type = LEPT_NUMBER; /*将v的type置为LEPT_NUMBER*/
return LEPT_PARSE_OK; /*返回解析OK*/
}
加入了 number 后,value 的语法变成:
value = null / false / true / number
记得在第一单元中,我们说可以用一个字符就能得知 value 是什么类型,有 11 个字符可判断 number:
-
0-9/- ➔ number
但是,由于我们在 lept_parse_number()
内部将会校验输入是否正确的值,我们可以简单地把余下的情况都交给 lept_parse_number()
:
static int lept_parse_value(lept_context* c, lept_value* v) {
switch (*c->json) {
case 't': return lept_parse_true(c, v);
case 'f': return lept_parse_false(c, v);
case 'n': return lept_parse_null(c, v);
default: return lept_parse_number(c, v);
case '\0': return LEPT_PARSE_EXPECT_VALUE;
}
}
5、总结与练习
(1)重构合并 lept_parse_null()
、lept_parse_false()
、lept_parse_true
为 lept_parse_literal()
。
在leptjson.c中
由于 true / false / null 的字符数量不一样,这个答案以 for 循环作比较,直至 '\0'
。
static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) {
size_t i; /*注意在 C 语言中,数组长度、索引值最好使用 `size_t` 类型,而不是 `int` 或 `unsigned`。*/
EXPECT(c, literal[0]);
for (i = 0; literal[i + 1]; i++) /*到 '\0' 结束*/
if (c->json[i] != literal[i + 1])
return LEPT_PARSE_INVALID_VALUE;
c->json += i;
v->type = type;
return LEPT_PARSE_OK;
}
static int lept_parse_value(lept_context* c, lept_value* v) {
switch (*c->json) {
case 't': return lept_parse_literal(c, v, "true", LEPT_TRUE);
case 'f': return lept_parse_literal(c, v, "false", LEPT_FALSE);
case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL);
/* ... */
}
}
(2)加入 维基百科双精度浮点数 的一些边界值至单元测试,如 min subnormal positive double、max double 等。
TEST_NUMBER(1.0000000000000002, "1.0000000000000002"); /* the smallest number > 1 */
TEST_NUMBER( 4.9406564584124654e-324, "4.9406564584124654e-324"); /* minimum denormal */
TEST_NUMBER(-4.9406564584124654e-324, "-4.9406564584124654e-324");
TEST_NUMBER( 2.2250738585072009e-308, "2.2250738585072009e-308"); /* Max subnormal double */
TEST_NUMBER(-2.2250738585072009e-308, "-2.2250738585072009e-308");
TEST_NUMBER( 2.2250738585072014e-308, "2.2250738585072014e-308"); /* Min normal positive double */
TEST_NUMBER(-2.2250738585072014e-308, "-2.2250738585072014e-308");
TEST_NUMBER( 1.7976931348623157e+308, "1.7976931348623157e+308"); /* Max double */
TEST_NUMBER(-1.7976931348623157e+308, "-1.7976931348623157e+308");
这里运行,有个bug,一直没有解决
(3)去掉 test_parse_invalid_value()
和 test_parse_root_not_singular
中的 #if 0 ... #endif
,执行测试,证实测试失败。按 JSON number 的语法在 lept_parse_number() 校验,不符合标准的程况返回 LEPT_PARSE_INVALID_VALUE
错误码。
(4)去掉 test_parse_number_too_big
中的 #if 0 ... #endif
,执行测试,证实测试失败。仔细阅读 strtod()
,看看怎样从返回值得知数值是否过大,以返回 LEPT_PARSE_NUMBER_TOO_BIG
错误码。(提示:这里需要 #include
额外两个标准库头文件。)