miloyip / json-tutorial Goto Github PK
View Code? Open in Web Editor NEW从零开始的 JSON 库教程
Home Page: https://zhuanlan.zhihu.com/json-tutorial
从零开始的 JSON 库教程
Home Page: https://zhuanlan.zhihu.com/json-tutorial
浮点数不是不可以使用 “==”符号比较大小吗? 为什么这里使用这种比较方法也可行呢?
在stringify_string中只对ch<0x20转换为\u0000,对于其他直接PUT进去了
对于那些拥有高低代理项的,为什么没有处理?这里不是很明白
当把tutorial02/test.c中117行和120行去掉以后,在CLion中运行报错为
/Users/peng/Documents/json-tutorial-master/tutorial02/test.c:118: expect: 4 actual: 0 /Users/peng/Documents/json-tutorial-master/tutorial02/test.c:118: expect: 0 actual: 3 /Users/peng/Documents/json-tutorial-master/tutorial02/test.c:119: expect: 4 actual: 0 /Users/peng/Documents/json-tutorial-master/tutorial02/test.c:119: expect: 0 actual: 3
不知道为何会重复运行。
这样传参的话:EXPECT_FALSE(lept_get_boolean(&v))
lept_get_boolean() 正常情况下返回 LEPT_FALSE(1)
而宏展开则是 (actual) == 0,
应该是 (actual) == 1 才符合测试。
解析3.1416解析出:
expect: 3.1415999999999999 actual: 3.1415999999999999
后面还有三组不正确,问题单步调试strtod解析错误,自己写了一个测试文件测试了,发现strtod解析3.1416正确,这是为什么呢?
我的是Ubuntu12 gcc4 .8.2
@miloyip
您好,
我在test.c里面加了两条用于DEBUG的宏。
test.c
#define EXPECT_EQ_RET(expect, actual) EXPECT_EQ_BASE((expect) == (actual), ret_e_name[expect], ret_e_name[actual], "%s")
#define EXPECT_EQ_TYPE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), type_e_name[expect], type_e_name[actual], "%s")
然后把enum对应的名称数组放在了leptjson.h里面
leptjson.h
static const char* ret_e_name[] = {
"LEPT_PARSE_OK",
"LEPT_PARSE_EXPECT_VALUE",
"LEPT_PARSE_INVALID_VALUE",
"LEPT_PARSE_ROOT_NOT_SINGULAR",
"LEPT_PARSE_NUMBER_TOO_BIG"
};
static const char* type_e_name[] = {
"LEPT_NULL",
"LEPT_FALSE",
"LEPT_TRUE",
"LEPT_NUMBER",
"LEPT_STRING",
"LEPT_ARRAY",
"LEPT_OBJECT"
};
我有一个疑问:
static char* ret_e_name[] = ...
,编译器会warn: var defined but not used(虽然现在没用use,但是以后可能会)。char* ret_e_name[] = ...
,因为leptjson.h被两个文件同时include,LINK的时候会报错redefine。我在想难道1就是正确的做法吗?通过编译选项把warning去掉?
我总感觉有其他漂亮的办法,希望您能解答。
LEPT_PARSE_INVALID_STRING_ESCAPE或LEPT_PARSE_INVALID_STRING_CHAR错误之后,若未将top置零,则可能assertion错误,但在测试中未能检测这一点。
是否需要增加这样的测试?
这个测试的结果会出现判断LEPT_PARSE_NUMBER_TOO_BIG
的结果,做不到返回结果为0……
是有需要特殊处理的深意,还是本身不太正确?
"%s:%d: expect: " format " actual: " format "\n"
为什么可以这样写啊?双引号不用转义么?忽然想不懂了,求人解惑,谢谢。
首先,如同 lept_parse_whitespace(),我们使用一个指针 p 来表示当前的解析字符位置。这样做有两个好处,一是代码更简单,二是在某些编译器下性能更好(因为不能确定 c 会否被改变,从而每次更改 c->json 都要做一次间接访问)。如果校验成功,才把 p 赋值至 c->json。
为什么使用p指针暂存有利于编译器优化,c->json的间接访问是什么意思?
if (*p == '.') {
p++;
if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE;
for (p++; ISDIGIT(*p); p++);
}
if (*p == 'e' || *p == 'E') {
p++;
if (*p == '+' || *p == '-') p++;
if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE;
for (p++; ISDIGIT(*p); p++);
}
如果*p
即不是.
也不是e
或者E
这时候数字也是非法的,比如字符串0m,但是这段代码没有处理这样的情况
第一种是利用已经写好的函数, 意图更明显, 代码比较好懂, 但是性能可能会降低(?)
for ( i = 0; i < lept_get_array_size(v); i++)
lept_free(lept_get_array_element(v, i));
第二种是直接操作, 这种代码看上去意图没有上面那种明显, 而且可能会出错, 但是作为库性能会更好
for (i = 0; i < v-> u.a.size; i++)
lept_free(&v->u.a.e[i]);
使用哪一种比较好呢?
做完第二部分练习时,对了一下参考答案,发现我的某部分实现可能会稍微短一些,和大家分享一下
static int lept_parse_number(lept_context* c, lept_value* v) {
char* end;
/* validate number */
if( !ISDIGIT(c->json[0]) && c->json[0]!='-' ) return LEPT_PARSE_INVALID_VALUE;/* 1 */
if( c->json[0]=='0' && c->json[1]!='.' && c->json[1]!=0 ) return LEPT_PARSE_ROOT_NOT_SINGULAR; /* 2 */
v->n = strtod(c->json, &end);
if (c->json == NULL || end[-1] == '.') /* end为小数点后的地址 */ /* 3 */
return LEPT_PARSE_INVALID_VALUE;
c->json = end;
if( errno == ERANGE && v->n != 0 ){ /* 4 */
errno = 0;
return LEPT_PARSE_NUMBER_TOO_BIG;
}
v->type = LEPT_NUMBER;
return LEPT_PARSE_OK;
}
https://bugs.kde.org/show_bug.cgi?id=365327
能否再介绍一个内存泄漏检测工具,谢谢了。
叶老师请看注释
static void lept_encode_utf8(lept_context* c, unsigned u) {
if (u <= 0x7F)
PUTC(c, u & 0xFF);
else if (u <= 0x7FF) {
PUTC(c, 0xC0 | ((u >> 6) & 0xFF)); // 这行最后为什么是 0xFF,为啥我觉得是 0x1F,即`11111`
PUTC(c, 0x80 | ( u & 0x3F));
}
else if (u <= 0xFFFF) {
PUTC(c, 0xE0 | ((u >> 12) & 0xFF)); // 这行最后为什么是 0xFF,为啥我觉得是 0xF,即`1111`
PUTC(c, 0x80 | ((u >> 6) & 0x3F));
PUTC(c, 0x80 | ( u & 0x3F));
}
else {
assert(u <= 0x10FFFF);
PUTC(c, 0xF0 | ((u >> 18) & 0xFF)); // 这行最后为什么是 0xFF,为啥我觉得是 0x7,即`111`
PUTC(c, 0x80 | ((u >> 12) & 0x3F));
PUTC(c, 0x80 | ((u >> 6) & 0x3F));
PUTC(c, 0x80 | ( u & 0x3F));
}
}
我在做练习时,上面三处0xFF
的地方写成我觉得的值,通过了测试。
现在没有值和‘null’都是用LEPT_NULL
test_parse_array() 中的 EXPECT_EQ_SIZE_T(0, lept_get_array_size(&v)),其中第一个参数0,5,4...等具体数值能否换成v.u.a.size?
请教一下为什么将test_parse_root_not_singular函数中“0123”``“0x0”``“0x123”
的测试返回结果算LEPT_PARSE_ROOT_NOT_SINGULAR,而不是算LEPT_PARSE_INVALID_VALUE?
宏的名字必须是唯一的,通常习惯以H_ 作为后缀。由于 leptjson 只有一个头文件,可以简单命名为 LEPTJSON_H__。如果项目有多个文件或目录结构,可以用 项目名称目录文件名称H_ 这种命名方式。
这里 Markdown 语法自动把两个 _ 变成斜体了,所以显示有点错误~
你好作者,你的所有number类型判定是否相等都是用EXPECT_EQ_BASE宏,而double类型数字显然不是精确的。这样测试可以吗?
lept_parse_number无法处理诸如12a.2这类错误,是否应该用状态机实现?
比如test中的例子: "\"Hello\\u0000World\""
然而因为我们用char*存储字符串,所以虽然写进内存的是"Hello\0world",但是因为'\0'是终止符,那么只能得到"Hello"。
我自己使用c++实现了一下,使用std::string
保存的字符串,最后得到的字符串是"Helloworld",`std::string``并没有遇到'\0'而终止。
我想问一下,在json语法中,这种情况是如何规定的呢?最终我们应该得到的字符串应该是哪一个呢?
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->size * 1.5 */
c->stack = (char*)realloc(c->stack, c->size);
}
ret = c->stack + c->top;
c->top += size;
return ret;
}
一旦 realloc 分配失败,返回 NULL。c->stack 赋值为 NULL,之前 c->stack 指向的内存未释放。是不是需要加一个逻辑判断内存分配是否正常。
void* new_ptr = (char*)realloc(c->stack, c->size);
if (!new_ptr) {
// 错误处理
}
c->stack = new_ptr;
与直接定义函数相比,宏的优势在哪?
当json为“null1”、“null12”这种以“null”为前缀的字符串时,lept_parse_null()函数是否应该返回LEPT_PARSE_INVALID_VALUE
起始项目的时候遇到:
goudandeMacBook-Pro:mini-JSON goudan$ cmake -DCMAKE_BUILD_TYPE=Debug ..
CMake Error: The source directory "/Users/kivygogh" does not appear to contain CMakeLists.txt.
/Users/kivygogh是我的根目录,第一次接触cmake,有点不知所以,求教作者大神原因?:)
if (*c.json != '\0') {
v->type = LEPT_NULL; //应该加上这一行
ret = LEPT_PARSE_ROOT_NOT_SINGULAR;
}
因为在test_parse_root_not_singular()
函数中测试的刚好是
EXPECT_EQ_INT(LEPT_PARSE_ROOT_NOT_SINGULAR, lept_parse(&v, "null x"));
所以如果解析错误了, 后续的
EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));
测试还是可以通过, 如果增加新的测试, 比如
EXPECT_EQ_INT(LEPT_PARSE_ROOT_NOT_SINGULAR, lept_parse(&v, "false a"));
就无法通过了.
if (*c.json != '\0')
ret = LEPT_PARSE_ROOT_NOT_SINGULAR;
这里c并不是指针,为什么要加* 而且我去掉_能make通过。
请问这里加_没问题吗?刚开始学,不太懂。
麻烦问一下,LEPT_PARSE_INVALID_STRING_ESCAPE 和LEPT_PARSE_INVALID_STRING_CHAR
的区别是什么?
int lept_get_boolean(const lept_value* v) {
assert(v != NULL && (v->type == LEPT_TRUE || v->type == LEPT_FALSE));
return v->type == LEPT_TRUE;
}
void lept_set_boolean(lept_value* v, int b) {
lept_free(v);
v->type = b ? LEPT_TRUE : LEPT_FALSE;
}
为什么不直接用LEPT_TRUE, LEPT_FALSE 这两个类型作为返回值呢?
请问,为什么lept_context_push要设计成返回一个指针,然后配合PUTC宏实现push操作;而不是直接将要push的内容作为参数传入lept_context_push中,在函数里完成操作?
如题,既然不允许解析inf,是不是没必要再使用errno判断范围越界了?
这里我有一个自己实现的状态机,test.c中的所有测试均已通过。
首先定义状态:
typedef enum { INIT, SIGN, INT, POINT, FRAC, FRACNUM, EXPSIGN, EXP, END } NUM_PARSE_STATE;
接下来是解析:
static int lept_parse_number(lept_context* c, lept_value* v) {
const char* p = c->json;
NUM_PARSE_STATE state = INIT;
for(;*p != '\0';p++){
switch (state) {
case INIT:
if(*p == '-') state = SIGN;
else if (*p == '0') state = POINT;
else if(ISDIGIT1TO9(*p)) state = INT;
else return LEPT_PARSE_INVALID_VALUE;
break;
case SIGN:
if(ISDIGIT1TO9(*p)) state = INT;
else if (*p == '0') state = POINT;
else return LEPT_PARSE_INVALID_VALUE;
break;
case INT:
if (ISDIGIT(*p)) state = INT;
else if (*p == '.') state = FRAC;
else if (*p == 'e' || *p == 'E') state = EXPSIGN;
else return LEPT_PARSE_INVALID_VALUE;
break;
case POINT:
if (*p == '.') state = FRAC;
else if (*p == 'e' || *p == 'E') state = EXPSIGN;
else return LEPT_PARSE_ROOT_NOT_SINGULAR; //这里也应该是INVALID_VALUE,只是为了通过测试改成了NOT_SINGULAR
break;
case FRAC:
if (ISDIGIT(*p)) state = FRACNUM;
else return LEPT_PARSE_INVALID_VALUE;
break;
case FRACNUM:
if (ISDIGIT(*p)) state = FRACNUM;
else if (*p == 'e' || *p == 'E') state = EXPSIGN;
else return LEPT_PARSE_INVALID_VALUE;
break;
case EXPSIGN:
if (ISDIGIT(*p) || *p == '+' || *p == '-') state = EXP;
else return LEPT_PARSE_INVALID_VALUE;
break;
case EXP:
if (ISDIGIT(*p)) state = EXP;
else return LEPT_PARSE_INVALID_VALUE;
break;
default: return LEPT_PARSE_INVALID_VALUE;
}
}
if (state != INT && state != POINT && state != FRACNUM && state != EXP)
return LEPT_PARSE_INVALID_VALUE;
errno = 0;
v->n = strtod(c->json, NULL);
if (errno == ERANGE && (v->n == HUGE_VAL || v->n == -HUGE_VAL)) return LEPT_PARSE_NUMBER_TOO_BIG;
v->type = LEPT_NUMBER;
c->json = p;
return LEPT_PARSE_OK;
}
lept_parse_hex4(const char* p, unsigned* u)
中循环条件应该改成 for (i = 0; i < 4 && p; ++i)
,判断 p
不为 NULL
。
在p
只剩4个字符的情况下,即使parse的hex是有效的,但是也返回NULL
,感觉并不是很合适。
不好意思,当时理解错了
叶老师好,在比较double类型的宏实现中
#define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g")
一般浮点数比较不能直接像整数一样,还是说在给定的测试用例下可以用此方式?因为expect保存在内存中的值就是会和转换后的actual完全一样的?
README里
一个微小的issue
我是用Scala练习写这个tutorial, 但是\x
escape在Java中是不存在的
Java用的是UTF-16,并不支持\x
的escape,能否直接把test case修改成\u00NN, 这样的修改有什么隐患吗?
@miloyip
边界值测试我尝试从 float.h
拿出了一些边界值进行测试。
我用了一个宏来把 float.h
里面的宏转换成字符串
#include <float.h>
#define NUM_TO_STR(num_macro) (sprintf(buffer, "%.17g", num_macro))
...
static void test() {
char buffer[100];
NUM_TO_STR(FLT_MIN);
TEST_NUMBER(FLT_MIN, buffer);
// the same with FLT_EPSILON, FLT_MAX, DBL_MIN, DBL_EPSILON, DBL_MAX
// -FLT_MIN, -FLT_EPSILON, -FLT_MAX, -DBL_MIN, -DBL_EPSILON, -DBL_MAX
}
有几个问题请教
1.2.这两个地方我觉得很ugly,有其他好的实现方法吗。
3..我感觉比较不安全的样子。
谢谢你的解答。
思考如何优化
test_parse_string()
的性能,那些优化方法有没有缺点。
由于这条问题是开放式问题,同学可以把自己的答案写在这里,大家互相借镜讨论。
请问:
为什么项目里的函数大多声明为static的?这样做有什么好处?
以及,一般在什么情况下将函数声明为static的,什么情况下作为普通函数?
在校验JSON number是否有效的练习中, 我使用了regex.h
中的几个正则表达式函数, 我的正则是这样的:
"^-?(0|[1-9]\\d*)(\\.\\d+)?([eE][+-]?\\d+)?$"
但是却带小数点的数字(-0.0
,1.5
)的测试都失败了, 不知道是什么原因...
非常感谢您的教程,令我受益匪浅!但是有些疑惑。
在tutorial04_answer中:
在test.c添加测试TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\udd1e\"");
,无法通过。
在UTF-16中,单一的代理项是无效的,只有一个低代理项,应该报错吧?
lept_context_push(c, size = len * 6 + 2);
就会发生可怕的事情了。static void json_stringify_string(json_context* c, const json_value* v)
{
static const char hex_digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
size_t size;
u_char* p;
u_char* head;
assert(v->json_s != NULL);
if (v->json_len > 0)
size = v->json_len * 6 - 2;
else
size = 2; /* "\"\"" */
p = head = json_context_push(c, size);
*p++ = '"';
for (size_t i = 0; i < v->json_len; ++i) {
u_char ch = (u_char) v->json_s[i];
switch (ch) {
case '\\': *p++ = '\\'; *p++ = '\\'; break;
case '"': *p++ = '\\'; *p++ = '"'; break;
case '/': *p++ = '\\'; *p++ = '/'; break;
case '\t': *p++ = '\\'; *p++ = 't'; break;
case '\b': *p++ = '\\'; *p++ = 'b'; break;
case '\n': *p++ = '\\'; *p++ = 'n'; break;
case '\r': *p++ = '\\'; *p++ = 'r'; break;
case '\f': *p++ = '\\'; *p++ = 'f'; break;
default:
if (ch < 0x20) {
*p++ = '\\'; *p++ = 'u'; *p++ = '0'; *p++ = '0';
*p++ = hex_digits[ch >> 4];
*p++ = hex_digits[ch & 15];
} else if (ch > 0x7f) { /* Handle UTF-8. */
unsigned u = json_decode_utf8((const u_char*) v->json_s, &i);;
if (u <= 0xffff) {
*p++ = '\\'; *p++ = 'u';
*p++ = hex_digits[u >> 12];
*p++ = hex_digits[(u >> 8) & 15];
*p++ = hex_digits[(u >> 4) & 15];
*p++ = hex_digits[u & 15];
} else if (u <= 0x10ffff) { /* Transfer codepoint to surrogate pair. */
unsigned h, l;
u -= 0x10000;
h = (u - (l = u % 0x400)) / 0x400;
h += 0xd800, l += 0xdc00;
*p++ = '\\'; *p++ = 'u';
*p++ = hex_digits[h >> 12];
*p++ = hex_digits[(h >> 8) & 15];
*p++ = hex_digits[(h >> 4) & 15];
*p++ = hex_digits[h & 15];
*p++ = '\\'; *p++ = 'u';
*p++ = hex_digits[l >> 12];
*p++ = hex_digits[(l >> 8) & 15];
*p++ = hex_digits[(l >> 4) & 15];
*p++ = hex_digits[l & 15];
}
} else {
*p++ = v->json_s[i];
}
break;
}
}
*p++ = '"';
c->top -= size - (p - head);
}
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')
ret = LEPT_PARSE_ROOT_NOT_SINGULAR;
}
return ret;
}
老师好,不知道是不是找到了一个小纰漏, :>
因为在之前规定了lept_parse() 若失败,会把 v 设为 null 类型,所以我觉得,应该在判断JSON文本没有完结之后,加一句 v->type =LEPT_NULL
,这样在 test_parse_root_not_singular()
函数中,可以正确 解析"false x"; 这类的情况。否则 EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));
将会打印错误信息。
lept_member 的 key
应不应该支持字符 ':'
比如
{
"name:1" : "LiHua"
}
TEST_STRING("\" \\ / \b \f \n \r \t", "\"\\\" \\\\ \\/ \\b \\f \\n \\r \\t\"");
TEST_STRING("\" \\ / \b \f \n \r \t", "\"\\\" \\\\ \/ \\b \\f \\n \\r \\t\"");
注意,下面的我把/前面的两个反斜线变成了一个,这样解析出的结果是一样的,也是正确的,但是这样不符合json对转义字符的定义啊,我觉得应该在对/这个字符要进行判定吧,判定其前面应该是\
tutorial07 test.c文件412行的测试例子为
TEST_ROUNDTRIP("\"\\\" \\\\ / \\b \\f \\n \\r \\t\"");
缺少了转义,应该为
TEST_ROUNDTRIP("\"\\\" \\\\ \\/ \\b \\f \\n \\r \\t\"");
然后我想问下,需不需要解析字符串类型的时候判断'/'这个字符,如果只出现'/'而前面是没有'//'应该是判定为LEPT_PARSE_INVALID_STRING_CHAR吧?
lept_get_number我在宏中错误的写成lept_get_num导致test函数检测不通过,但是编译器并没有提示函数未定义,返回的数据时大时小,请问这种调用不存在函数的时候,是怎么样的执行流程啊?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.