从零开始的 JSON 库教程(二):解析数字

155 阅读3分钟

代码地址:github.com/WangHao-nlp…

第二节主要解析数字,提供了两个版本,第一个版本有缺陷。 首先看看json的数字语法

number = [ "-" ] int [ frac ] [ exp ]
int = "0" / digit1-9 *digit
frac = "." 1*digit
exp = ("e" / "E") ["-" / "+"] 1*digit

负号可有可无,整数必有,小数和指数可有可无
整数要么是0,要么1-9开头
小数有小数点即可,后面跟任意数
指数部分e/E混用,然后跟正负号,然后跟任意数
image.png

所以以下数字都是错误的,不符合json数字格式

TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0");    // json数字中无+号
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1");
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123");  // 缺少整数部分
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1.");    // 小数点后至少有一个数
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF");   // json不认inf,nan
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf");
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN");
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan");
TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); 
// 0是完整的number了,后面不应该还有字符,所以是LEPT_PARSE_ROOT_NOT_SINGULAR错误
// json数字不认0x(16进制)
TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); 
TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123");

leptjson.h

json对象里加入了double类型的数值

typedef struct {
    double n;
    lept_type type;
}lept_value;

增加了新的解析结果:数值过大

enum {
    LEPT_PARSE_OK = 0,
    LEPT_PARSE_EXPECT_VALUE,
    LEPT_PARSE_INVALID_VALUE,
    LEPT_PARSE_ROOT_NOT_SINGULAR,
    LEPT_PARSE_NUMBER_TOO_BIG   // 解析失败,数值过大
};

leptjson.cpp

重构解析null、true、false;
literal用来判断是不是null|true|false,type用来解析完给v复制

static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) {
    size_t i;
    EXPECT(c, literal[0]);
    for (i = 0; literal[i + 1]; i++) {
        if (c->json[i] != literal[i + 1]) {
      // 因为v.type初始化为LEPT_NULL,所以解析为无效值后,这里不用重新给type赋值
            return LEPT_PARSE_INVALID_VALUE;
        }
    }
    c->json += i;
    v->type = type;
    return LEPT_PARSE_OK;
}

C 库函数 double strtod(const char *str, char **endptr) 把参数 str 所指向的字符串转换为一个浮点数(类型为 double 型)。如果 endptr 不为空,则指向转换中最后一个字符后的字符的指针会存储在 endptr 引用的位置。

// 版本1
static int lept_parse_number(lept_context* c, lept_value* v) {
    char* end;
    v->n = strtod(c->json, &end);
    if (c->json == end) // 即无数字部分
        return LEPT_PARSE_INVALID_VALUE;
    c->json = end;  // 有数字部分,但可能数字后还有其他字符
    v->type = LEPT_NUMBER;
    return LEPT_PARSE_OK;
}

版本1下,部分解析结果与期望不同

// 这些在json中不是数字,但方案1会把它们解析为数字
// expect: LEPT_PARSE_INVALID_VALUE actual : LEPT_PARSE_OK  
// expect : LEPT_NULL actual : LEPT_NUMBER
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0");    
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1");
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123");  
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1.");    
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF");   
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf");
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN");
// TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); // 这个方案一也认为这是无效值,可能是因为n开头,进了null解析函数

// 这些在json中也不是数字,但方案1会把它们解析为数字
// expect: LEPT_PARSE_ROOT_NOT_SINGULAR : LEPT_PARSE_OK  
// expect: LEPT_NULL actual : LEPT_NUMBER
// 0是完整的number了,后面不应该还有字符,所以是LEPT_PARSE_ROOT_NOT_SINGULAR错误
// json数字不认0x(16进制)
TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); 
TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); 
TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123");	

// 这些在json中过大,但方案1无法识别出它已超出界限,当作正常数字
// expect: LEPT_PARSE_NUMBER_TOO_BIG : LEPT_PARSE_OK  
// expect: LEPT_NULL actual : LEPT_NUMBER
TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309");
TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309");
// 版本2
static int lept_parse_number(lept_context* c, lept_value* v) {
    const char* p = c->json;
    if (*p == '-')p++;   // 先检查有没有负号(不可以有+号开头)
    if (*p == '0') {     // 再检查整数部分,是不是0或者1-9开头的整数
        p++;
    }
    else {
        if (!ISDIGIT1TO9(*p)) {
            return LEPT_PARSE_INVALID_VALUE;  // 开头既不是0也不是1-9,则是无效值
        }
        for (p++; ISDIGIT(*p); p++);   //开头是123..9,然后忽略中间的数字,如0-9,直接跳过整数部分
    }
    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;  // e后面要跟任意一个整数
        for (p++; ISDIGIT(*p); p++);  // 跳过整数
    }
    errno = 0;
    v->n = strtod(c->json, NULL); 
    // strtod很强大,合法的不合法的都可以识别出数字,不合法的json字符串已经剔除,仍可以使用strtod识别数字
    // errno,ERANGE是固定套路
    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;
}

if (errno == ERANGE && (v->n == HUGE_VAL || v->n == -HUGE_VAL))
此条件可以;表示strtod超出范围,而且数字太大
if ((v->n == HUGE_VAL || v->n == -HUGE_VAL))
此条件可以;表示数字太大 if (errno == ERANGE) 此条件不行;仅仅约束strtod超出范围;但TEST_NUMBER(0.0, "1e-10000")虽超出范围,但仍是浮点数。

解析结果

image.png