JSON 对象语法
本章实现的是JSON对象。JSON对象的实现和JSON数组的实现很是相似,我们可以对比着来看。
JSON数组 | JSON对象 |
---|---|
JSON数组是由JSON值value组成 | JSON对象是由成员对象member组成,成员对象是键值对 |
JSON数组是 [] 构成 | JSON对象是 {} 构成 |
一个数组可以包含零至多个值, 而这些值可以字符,数字,也可以是数组; 值与值之间以逗号分隔, 例如 []、[1,2,true]、[[1,2],[3,4],“abc”] 都是合法的数组。但注意 JSON数组 不接受末端额外的逗号,例如 [1,2,] 是不合法的。 | JSON对象由成员对象组成,成员对象由键值对组成,其中键为 JSON字符串(string),值是任何JSON值(value),中间由冒号分割(%x3A)。 |
JSON对象语法。
JSON-text = ws value ws
value = null / false / true / number / string / array / object
object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D
member = string ws %x3A ws value
解释:
- %x7B 是左大括号 {
- %x2C 是逗号 ,
- %x7D 是右大括号 }
- ws 是空白字符
- %x3A 是冒号
头文件
在 ECMA-404 标准中,并没有规定对象中每个成员的键一定要唯一的,也没有规定是否需要维持成员的次序。为了简单起见,我们选择用动态数组来实现JSON对象,
对于对象,既然我们采用了动态数组的方案,那么每个对象就是成员的数组:
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;
};
/*动态数组表示JSON对象(键值对)*/
struct lept_member {
char* k; /* 键(字符串) */
size_t klen; /* 字符串的长度,因为可能包含空字符\u0000 */
lept_value v; /* 值 */
};
添加错误码:
enum {
/* ... */
LEPT_PARSE_MISS_KEY, //没有键
LEPT_PARSE_MISS_COLON, //没有冒号
LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET
};
同样的,添加API
size_t lept_get_object_size(const lept_value* v);
const char* lept_get_object_key(conat lept_calue* v, size_t index);
size_t lept_get_object_key_length(const lept_value* v, size_t index);
lept_value* lept_get_object_value(const lept_value* v, size_t index);
实现API函数:
size_t lept_get_object_size(const lept_value* v)
{
assert(v != NULL && v->type == LEPT_OBJECT);
return v->u.o.size;
}
const char* lept_get_object_key(const lept_value* v, size_t index)
{
assert(v != NULL && v->type == LEPT_OBJECT);
assert(index < v->u.o.size);
return v->u.o.m[index].k;
}
size_t lept_get_object_key_length(const lept_value* v, size_t index)
{
assert(v != NULL && v->type == LEPT_OBJECT);
assert(index < v->u.o.size);
return v->u.o.m[index].klen;
}
lept_value* lept_get_object_value(const lept_value* v, size_t index)
{
assert(v != NULL && v->type == LEPT_OBJECT);
assert(index < v->u.o.size);
return &v->u.o.m[index].v;
}
测试代码
static void test_parse_object() {
lept_value v;
size_t i;
lept_init(&v);
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, " { } "));
EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(&v));
EXPECT_EQ_SIZE_T(0, lept_get_object_size(&v));
lept_free(&v);
lept_init(&v);
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v,
" { "
"\"n\" : null , "
"\"f\" : false , "
"\"t\" : true , "
"\"i\" : 123 , "
"\"s\" : \"abc\", "
"\"a\" : [ 1, 2, 3 ],"
"\"o\" : { \"1\" : 1, \"2\" : 2, \"3\" : 3 }"
" } "
));
EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(&v));
EXPECT_EQ_SIZE_T(7, lept_get_object_size(&v));
EXPECT_EQ_STRING("n", lept_get_object_key(&v, 0), lept_get_object_key_length(&v, 0));
EXPECT_EQ_INT(LEPT_NULL, lept_get_type(lept_get_object_value(&v, 0)));
EXPECT_EQ_STRING("f", lept_get_object_key(&v, 1), lept_get_object_key_length(&v, 1));
EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(lept_get_object_value(&v, 1)));
EXPECT_EQ_STRING("t", lept_get_object_key(&v, 2), lept_get_object_key_length(&v, 2));
EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(lept_get_object_value(&v, 2)));
EXPECT_EQ_STRING("i", lept_get_object_key(&v, 3), lept_get_object_key_length(&v, 3));
EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_object_value(&v, 3)));
EXPECT_EQ_DOUBLE(123.0, lept_get_number(lept_get_object_value(&v, 3)));
EXPECT_EQ_STRING("s", lept_get_object_key(&v, 4), lept_get_object_key_length(&v, 4));
EXPECT_EQ_INT(LEPT_STRING, lept_get_type(lept_get_object_value(&v, 4)));
EXPECT_EQ_STRING("abc", lept_get_string(lept_get_object_value(&v, 4)),
lept_get_string_length(lept_get_object_value(&v, 4)));
EXPECT_EQ_STRING("a", lept_get_object_key(&v, 5), lept_get_object_key_length(&v, 5));
EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(lept_get_object_value(&v, 5)));
EXPECT_EQ_SIZE_T(3, lept_get_array_size(lept_get_object_value(&v, 5)));
for (i = 0; i < 3; i++) {
lept_value* e = lept_get_array_element(lept_get_object_value(&v, 5), i);
EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(e));
EXPECT_EQ_DOUBLE(i + 1.0, lept_get_number(e));
}
EXPECT_EQ_STRING("o", lept_get_object_key(&v, 6), lept_get_object_key_length(&v, 6));
{
lept_value* o = lept_get_object_value(&v, 6);
EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(o));
for (i = 0; i < 3; i++) {
lept_value* ov = lept_get_object_value(o, i);
EXPECT_TRUE('1' + i == lept_get_object_key(o, i)[0]);
EXPECT_EQ_SIZE_T(1, lept_get_object_key_length(o, i));
EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(ov));
EXPECT_EQ_DOUBLE(i + 1.0, lept_get_number(ov));
}
}
lept_free(&v);
}
static void test_parse() {
/* ... */
test_parse_object();
}
错误码的测试函数
static void test_parse_miss_key() {
TEST_ERROR(LEPT_PARSE_MISS_KEY, "{:1,");
TEST_ERROR(LEPT_PARSE_MISS_KEY, "{1:1,");
TEST_ERROR(LEPT_PARSE_MISS_KEY, "{true:1,");
TEST_ERROR(LEPT_PARSE_MISS_KEY, "{false:1,");
TEST_ERROR(LEPT_PARSE_MISS_KEY, "{null:1,");
TEST_ERROR(LEPT_PARSE_MISS_KEY, "{[]:1,");
TEST_ERROR(LEPT_PARSE_MISS_KEY, "{{}:1,");
TEST_ERROR(LEPT_PARSE_MISS_KEY, "{\"a\":1,");
}
static void test_parse_miss_colon() {
TEST_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\"}");
TEST_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\",\"b\"}");
}
static void test_parse_miss_comma_or_curly_bracket() {
TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1");
TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1]");
TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1 \"b\"");
TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":{}");
}
static void test_parse() {
/* ... */
test_parse_object();
test_parse_miss_key();
test_parse_miss_colon();
test_parse_miss_comma_or_curly_bracket();
}
函数实现
在函数是先前,我们先来讨论一个问题—重构
代码重构 code refactoring是指在不改变软件外在行为时,修改代码以改进结构。代码重构十分依赖于单元测试,因为我们是通过单元测试去维护代码的正确性。有了足够的单元测试,我们可以放胆去重构,尝试并评估不同的改进方式,找到合乎心意而且能通过单元测试的改动,我们才提交它。
我们知道,成员的键也是一个 JSON 字符串,然而,我们不使用 lept_value 存储键,因为这样会浪费了当中 type 这个无用的字段。由于 lept_parse_string() 是直接地把解析的结果写进一个 lept_value,所以我们先用「提取方法 」的重构方式,把解析 JSON 字符串及写入 lept_value 分拆成两部分。
先看原来的 lept_parse_string()函数:
static int lept_parse_string(lept_context* c, lept_value* v) {
size_t head = c->top, len;
unsigned u, u2;
const char* p;
EXPECT(c, '\"');
p = c->json;
for (;;) {
char ch = *p++;
switch (ch) {
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 '\\':
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) { /* surrogate pair */
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);
}
}
}
这段函数有两个作用:1. 是解析字符串 2. 是把字符串保存在 lept_value里,因为JSON对象在解析时,前面键的部分也就是字符串是保存在lept_member里面,所以我们把这个函数拆开看。
/* 解析 JSON 字符串,把结果写入 str 和 len */
/* str 指向 c->stack 中的元素,需要在 c->stack */
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;//注意,这里得用*len,而不是len,主要是因为要改变的参数里的值
*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) { /* surrogate pair */
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 (LEPT_PARSE_OK == (ret = lept_parse_string_raw(c, &s, &len))){
lept_set_string(v, s, len);
}
return ret;
}
了解上面的内容后,我们可以开始下面的内容,解析对象函数的编写。
注意,我们在解析数组的时候,是把当前元素以lept_value压入栈中,而在这里,我们则是以lept_member压入:
static int lept_parse_object(lept_context* c, lept_value* v)
{
size_t size,i;
int ret;
lept_member m;
EXPECT(c, '{');
lept_parse_whitespace(c);
if (*c->json == '}'){
c->json++;
v->type = LEPT_OBJECT;
v->u.o.m = NULL;
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;
}
//否则解析字符串正确,将字符串保存在m.k里面
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);
//解析 值, 值是任意的JSON值,把结果写进m.v里面
if ((ret = lept_parse_value(c, &m.v)) != LEPT_PARSE_OK){ //解析错误
break;
}
//解析成功,把m里面的结果写入c里面
memcpy(lept_context_push(c, sizeof(lept_member)), &m, sizeof(lept_member));
size++;
m.k = NULL;//如果之前缺乏冒号,或是这里解析值失败,在函数返回前我们要释放m.k。如果我们成功地解析整个成员,那么就要把 m.k 设为空指针,其意义是说明该键的字符串的拥有权已转移至栈,之后如遇到错误,我们不会重覆释放栈里成员的键和这个临时成员的键。
//解析逗号
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;
size *= sizeof(lept_member);
memcpy(v->u.o.m = (lept_member*)malloc(size), lept_context_pop(c, size), size);
return LEPT_PARSE_OK;
}
else {
ret = LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET;
break;
}
}
//for循环遇到错误时:释放临时key字符串及栈上的成员
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);
}
v->type = LEPT_NULL;
return ret;
}
//不要忘记修改lept_parse_value函数
static int lept_parse_value(lept_context* c, lept_value* v) {
/* ....*/
case '{': return lept_parse_object(c, v);
}