这一节我们讲解所有类型的生成Stringify。
其实stringify的意思本不是生成,而是字符串化,但为了方便理解,我直接称之为生成(generate)也是可以的。
生成器它生成得好,可以生成出很整洁的字符串,方便我们阅读,不过在这里我们只进行字符生成,不进行排版操作,所以最后生成出来的就是一长串的挤在一行里的字符串。
在这里我们也能想到,生成器的操作就是解析器反过来,所以它大致的架构也是一个循环里套一个switch语句。
这里为了代码的美观我们也是像parse和parse_value那样将stringify和stringify_value区分开来。
#ifndef LEPT_PARSE_STRINGIFY_INIT_SIZE
#define LEPT_PARSE_STRINGIFY_INIT_SIZE 256
#endif
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;
}
这里的stringify函数的主要操作是创建一个context并为其分配一定初识内存(初始化)以及最后的在字符串尾放一个空字符
case LEPT_NULL: PUTS(c, "null", 4); break;
case LEPT_FALSE: PUTS(c, "false", 5); break;
case LEPT_TRUE: PUTS(c, "true", 4);break;
简单类型,不多赘述,
case LEPT_NUMBER: c.top -= 32 - (sprintf((char*)lept_context_push(c, 32), "%.17g", v.u.m_num));
break;
这里的操作其实不是很美观,在对性能的极致追求上,将一些理解性的临时变量给省略了。如果换成以下的代码也许会更好理解:
case LEPT_NUMBER:
{
//向context中压入32的内存(32为数字最大占用字符串大小)
char* buffer = lept_context_push(c, 32);
//在其中写入数据(number->string),并记录占用的字符数
int length = sprintf(buffer, "%.17g", v->u.n);
//栈顶移动
c->top -= 32 - length;
}
break;
String的生成因为涉及到转义序列,而转义序列会用到switch,为了代码的美观,我们将string_stringify封装以下。
在stringify_value中代码如下
case LEPT_STRING: lept_stringify_string(c, v.u.m_str.s, v.u.m_str.len);
break;
string_stringify函数:
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 = (char*)lept_context_push(c, size = len * 6 + 2); /* "\u00xx..." */
*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);
}
因为二者十分相似,都递归了stringify_value,我就放一起了,object只是多一个key值的stringify
case LEPT_ARRAY:
PUTC(c, '[');
for (i = 0; i < v.u.m_arr.size; i++) {
if (i > 0)
PUTC(c, ',');
lept_stringify_value(c, v.u.m_arr.e[i]);
}
PUTC(c, ']');
break;
case LEPT_OBJECT:
PUTC(c, '{');
for (i = 0; i < v.u.m_obj.size; i++) {
if (i > 0)
PUTC(c, ',');
lept_stringify_string(c, v.u.m_obj.m[i].k, v.u.m_obj.m[i].klen);
PUTC(c, ':');
lept_stringify_value(c, v.u.m_obj.m[i].v);
}
PUTC(c, '}');
break;
如果要进行美化的话,可以使用局部静态变量。
以下是我的美化操作:
static int num = 0;//在stringify的switch外部引入一个局部静态变量
/* ..... */
case LEPT_OBJECT:
PUTC(c, '{');
for (i = 0; i < v.u.m_obj.size; i++) {
PUTC(c, '\n'); /* 美化操作 */
for(int x = 0;x<++num;x++)/* 美化操作 */
PUTC(c, '\t');
lept_stringify_string(c, v.u.m_obj.m[i].k, v.u.m_obj.m[i].klen);
PUTC(c, ':');
lept_stringify_value(c, v.u.m_obj.m[i].v);
if (i > 0)
PUTC(c, ',');
}
PUTC(c, '\n'); /* 美化操作 */
for (int x = 0; x < --num; x++)/* 美化操作 */
PUTC(c, '\t');
PUTC(c, '}');
break;
/* ..... */
这里主要对object进行了美化,也就是说其它的变量比如array依然是会输出在同一行。
最后可以自行进行一些测试,因为现在能够生成字符串了,自行感受以下整洁的数据结构还是很享受的一件事。
不过该有的单元测试还是要有的
为贯彻 TDD,先写测试:
专门定义一个生成测试宏
#define TEST_ROUNDTRIP(json)\
do {\
lept_value v;\
char* json2;\
size_t length;\
lept_init(&v);\
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\
EXPECT_EQ_INT(LEPT_STRINGIFY_OK, lept_stringify(&v, &json2, &length));\
EXPECT_EQ_STRING(json, json2, length);\
lept_free(&v);\
free(json2);\
} while(0)
static void test_stringify_number() {
TEST_ROUNDTRIP("0");
TEST_ROUNDTRIP("-0");
TEST_ROUNDTRIP("1");
TEST_ROUNDTRIP("-1");
TEST_ROUNDTRIP("1.5");
TEST_ROUNDTRIP("-1.5");
TEST_ROUNDTRIP("3.25");
TEST_ROUNDTRIP("1e+20");
TEST_ROUNDTRIP("1.234e+20");
TEST_ROUNDTRIP("1.234e-20");
TEST_ROUNDTRIP("1.0000000000000002"); /* the smallest number > 1 */
TEST_ROUNDTRIP("4.9406564584124654e-324"); /* minimum denormal */
TEST_ROUNDTRIP("-4.9406564584124654e-324");
TEST_ROUNDTRIP("2.2250738585072009e-308"); /* Max subnormal double */
TEST_ROUNDTRIP("-2.2250738585072009e-308");
TEST_ROUNDTRIP("2.2250738585072014e-308"); /* Min normal positive double */
TEST_ROUNDTRIP("-2.2250738585072014e-308");
TEST_ROUNDTRIP("1.7976931348623157e+308"); /* Max double */
TEST_ROUNDTRIP("-1.7976931348623157e+308");
}
static void test_stringify_string() {
TEST_ROUNDTRIP("\"\"");
TEST_ROUNDTRIP("\"Hello\"");
TEST_ROUNDTRIP("\"Hello\\nWorld\"");
TEST_ROUNDTRIP("\"\\\" \\\\ / \\b \\f \\n \\r \\t\"");
TEST_ROUNDTRIP("\"Hello\\u0000World\"");
}
static void test_stringify_array() {
TEST_ROUNDTRIP("[]");
TEST_ROUNDTRIP("[null,false,true,123,\"abc\",[1,2,3]]");
}
static void test_stringify_object()
{
TEST_ROUNDTRIP("{}");
TEST_ROUNDTRIP("{\"n\":null,\"f\":false,\"t\":true,\"i\":123,\"s\":\"abc\",\"a\":[1,2,3],\"o\":{\"1\":1,\"2\":2,\"3\":3}}");
}
static void test_stringify() {
TEST_ROUNDTRIP("null");
TEST_ROUNDTRIP("false");
TEST_ROUNDTRIP("true");
test_stringify_number();
test_stringify_string();
test_stringify_array();
test_stringify_object();
}
以上就是生成器的编写。之后讲以下所有的api,那么整个json的复习(对于我自己)就结束了。之后就要进入下一个项目的学习了
我走在一条未知的道路上,我所能做的就是走下去,直到路口或者尽头
lept_json Github:https://github.com/miloyip/json-tutorial
本人流星画魂第七次在csdn上做笔记,有什么错误或者是需要改进的地方请即时提出
我只是一个对编程感兴趣的人,但懒得要死,学得又不认真,希望读者能骂就骂两句,真的太懒了