CJSON开发学习笔记
每个函数作用
两个结构体 :
-
lept_context:用来存放json语句的指针 和栈的信息(char*stack 栈指针,int top栈顶,int size栈大小)
- 其中堆用来暂时存储信息,利用memcpy进行复制 堆的数据
-
lept_value: 包含value类型、及存放对应类型(数组、字符、数字)的定义
-
int lept_prase(); 初始化lept_context,并存放json语句的开始指针。进入int lept_prase_value解析。根据首字符确定是什么类型在进行对应解析
-
void* lept_context_push()入栈操作(内有扩容机制若超出扩容1.5倍) 返回指向栈顶的指针
c->stack+c->top
。注意里面的c->stack
一直都是当前内存的头地址。 -
void * lept_context_pop() 返回栈顶指针
-
static const char* lept_parse_hex4(const char* p, unsigned* u) 将字符串转为double类型;
启程
__FILE__
与__LINE__
的用途
方便在大型项目中找出错误。
fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);
//File中文意思即文件,这里的意思主要是指:
//正在编译文件对应正在编译文件的路径和文件的名称。
//LINE 显示出错的行号
数字、数组、字符串、对象object解析 随缘记录
#if
与#end if
编译器预处理的指令
#if 0 //表示之后的内容不运行。编译器预处理的指令,当改为1 则运行。
。。。
#end if
用途:框出这些代码备用防止以后用到。
strtod()
strtod()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,到出现==非数字或字符串结束时(’\0’)==才结束转换,并将结果返回。若endptr不为NULL,则会将遇到不合条件而终止的nptr中的字符指针由endptr传回。参数nptr字符串可包含正负号、小数点或E(e)来表示指数部分。如123.456或123e-2。
#include <stdio.h>
#include <stdlib.h>
int main()
{
char str[30] = "20.30300 This is test";
char *ptr;
double ret;
ret = strtod(str, &ptr);
printf("数字(double)是 %lf\n", ret);
printf("字符串部分是 |%s|", ptr);
return(0);
}
难点:
- 实际上一些 JSON 库会采用更复杂的方案,例如支持 64 位带符号/无符号整数,自行实现转换。以我的个人经验,解析/生成数字类型可以说是 RapidJSON 中最难实现的部分,也是 RapidJSON 高效性能的原因
errno库
例子
c->json = p的重要性以及理解
若下面的数字解析,没有将c->json指向末尾即 p,(c->json = p
),
static int lept_parse_number(lept_context* c, lept_value* v) {
const char* p = c->json;
if (*p == '-') p++;
if (*p == '0') p++;
else {
if (!ISDIGIT1TO9(*p)) return LEPT_PARSE_INVALID_VALUE;
for (p++; ISDIGIT(*p); p++);
}
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++);
}
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;
}
则在返回到下面的函数时 该数据类型会由数字类型(一开始以数字类型处理的)赋值为 空类型
注意判空面对的是 由类似 ”ture n“这类字符串:
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;
}
前后出现冲突,在获取数字类型时会通过assert 造成程序中断.
double lept_get_number(const lept_value* v) {
assert(v != NULL && v->type == LEPT_NUMBER);
return v->n;
}
double 类型 变量 边界值测试。
JSON语法
Unicode
- 码点的范围是 0 至 0x10FFFF,码点又通常记作 U+XXXX,当中 XXXX 为 16 进位数字。例如 劲 → U+52B2、峰 → U+5CF0。很明显,UCS 中的字符无法像 ASCII 般以一个字节存储。故需要将码点编码成utf-8来进行存储(UTF-8是Unicode一种转换形式)
UTF-8 编码表
高/低代理(JSON 会使用代理对(surrogate pair)表示 \uXXXX\uYYYY。)
- 同学可能会发现,4 位的 16 进制数字只能表示 0 至 0xFFFF,但之前我们说 UCS 的码点是从 0 至 0x10FFFF,那怎么能表示多出来的码点?
其实,U+0000 至 U+FFFF 这组 Unicode 字符称为基本多文种平面(basic multilingual plane)。
如果第一个码点是 U+D800 至 U+DBFF,我们便知道它的代码对的高代理项(high surrogate),之后应该伴随一个 U+DC00 至 U+DFFF 的低代理项(low surrogate)。然后,我们用下列公式把代理对 (H, L) 变换成真实的码点:
对于数组的内存释放
因为数组包含 数组的元素 也可能包含字符串。字符串直接释放v->u.s.s 而数组需要 区别两种类型分别释放。
故字符串可采用递归调用释放。
另外要考虑到部分不符合语法的数组中符合语法的元素入栈 ,无论如何解析完都要注意有释放操作。
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;
}
当将存储的信息转化为Cjson语句时 对于UTF-8的理解
-
unicode 是怎么弄的,没怎么看懂,假如按照前面的那个欧元符号的例子,在内存中是0xE2, 0x82, 0xAC,这个是如何转化成\u20AC的?
不需处理,因为字符串本身是UTF - 8,输出的JSON也是。只有 < 32 的才必须转义。
static void lept_stringify_string(lept_context* c, const char* s, size_t len) {
size_t i;
assert(s != NULL);
PUTC(c, '"');
for (i = 0; i < len; i++) {
unsigned char ch = (unsigned char)s[i];
switch (ch) {
case '\"': PUTS(c, "\\\"", 2); break;
case '\\': PUTS(c, "\\\\", 2); break;
case '\b': PUTS(c, "\\b", 2); break;
case '\f': PUTS(c, "\\f", 2); break;
case '\n': PUTS(c, "\\n", 2); break;
case '\r': PUTS(c, "\\r", 2); break;
case '\t': PUTS(c, "\\t", 2); break;
default:
if (ch < 0x20) {
char buffer[7];
sprintf(buffer, "\\u%04X", ch);
//unicode 是怎么弄的,没怎么看懂,假如按照前面的那个欧元符号的例子,在内存中是0xE2, 0x82, 0xAC,这个是如何转化成\u20AC的?
//不需处理,因为字符串本身是UTF - 8,输出的JSON也是。只有 < 32 的才必须转义。
PUTS(c, buffer, 6);
}
else
PUTC(c, s[i]);
}
}
PUTC(c, '"');
}
难点
Cjson文本中”【“和“】”即有数组又有字符串 ,
解析时如何分别解析?
static int lept_parse_value(lept_context* c, lept_value* v);/*前向声明*/ //因为下面有调用
static int lept_parse_array(lept_context* c, lept_value* v) {
size_t size = 0;
int ret;
EXPECT(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) // lept_parse_value 解决了数组与字符串的区别解析;
return ret;
memcpy(lept_context_push(c, sizeof(lept_value)), &e, sizeof(lept_value));
size++;
if (*c->json == ',')
c->json++;
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
return LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET;
}
}
static int lept_parse_value(lept_context* c, lept_value* v) {
switch (*c->json) {
/* ... */
case '[': return lept_parse_array(c, v);
}
}
当使用完之后 该如何分别释放内存
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;
case LEPT_OBJECT:
for (i = 0; i < v->u.o.size; i++) {
free(v->u.o.m[i].k);
lept_free(&v->u.o.m[i].v);
}
free(v->u.o.m);
break;
default: break;
}
v->type = LEPT_NULL;
}
解决方法:自身调用自身
未解决的模块、lept_copy 、lept_earse_array
参考资料
https://blog.csdn.net/it_is_me_a/article/details/102223101