每个函数作用

两个结构体 :

  • 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 类型 变量 边界值测试。

ieee754相关概念.
在这里插入图片描述

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

GitHub 加速计划 / cj / cJSON
10.45 K
3.16 K
下载
Ultralightweight JSON parser in ANSI C
最近提交(Master分支:2 个月前 )
424ce4ce This reverts commit 5b502cdbfb21fbe5f6cf9ffbd2b96e4281a741e6. Related to #860 4 个月前
32497300 - 5 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐