【c/c++项目学习】 JSON 库-tutorial02-解析数字

1、JSON的数字语法

本单元的重点在于解析 JSON number 类型。我们先看看它的语法:

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

number 是以十进制表示,它主要由 4 部分顺序组成:负号、整数、小数、指数。只有整数是必需部分。注意和直觉可能不同的是,正号是不合法的。

整数部分如果是 0 开始,只能是单个 0;而由 1-9 开始的话,可以加任意数量的数字(0-9)。也就是说,0123 不是一个合法的 JSON 数字。

小数部分比较直观,就是小数点后是一或多个数字(0-9)。

JSON 可使用科学记数法,指数部分由大写 E 或小写 e 开始,然后可有正负号,之后是一或多个数字(0-9)。

2、数字表示方式

从 JSON 数字的语法,我们可能直观地会认为它应该表示为一个浮点数(floating point number),因为它带有小数和指数部分。然而,标准中并没有限制数字的范围或精度。为简单起见,leptjson 选择以双精度浮点数(C 中的 double 类型)来存储 JSON 数字。

我们为 lept_value 添加成员:

leptjson.h

#ifndef LEPTJSON_H__
#define LEPTJSON_H__
/*防卫式声明*/
 
/*JSON 中有 7 种数据类型,用枚举类型定义,类型别名为 lept_type */
typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type;
 
/*声明 JSON 的数据结构。JSON 是一个树形结构,我们最终需要实现一个树的数据结构,
  每个节点使用 lept_value 结构体表示,我们会称它为一个 JSON 值(JSON value)。
  在此单元中,我们只需要实现 数字 的解析,
  因此该结构体不仅存储 lept_type,还需要有具体的数据n
  类型别名是 lept_value
*/
typedef struct {
    double n;
    lept_type type;
}lept_value;
 
/*下面解析函数的返回类型*/
enum {
    LEPT_PARSE_OK = 0,                /*OK*/
    LEPT_PARSE_EXPECT_VALUE,          /*解析期望值*/
    LEPT_PARSE_INVALID_VALUE,         /*解析非法数据*/
    LEPT_PARSE_ROOT_NOT_SINGULAR,     /*解析根不是单数,根就是双数*/
    LEPT_PARSE_NUMBER_TOO_BIG
};
 
/*解析 JSON : 将JSON文本解析成树状数据结构
  lept_value* v : 根结点
  const char* json : JSON 的 C 字符串
*/
int lept_parse(lept_value* v, const char* json);
 
/*访问结果的函数,就是获取其类型*/
lept_type lept_get_type(const lept_value* v);

/*获取具体的数据*/
double lept_get_number(const lept_value* v);
 
#endif /* LEPTJSON_H__ */

仅当 type == LEPT_NUMBER 时,n 才表示 JSON 数字的数值。所以获取该值的 API 是这么实现的:

double lept_get_number(const lept_value* v) 
{
    assert(v != NULL && v->type == LEPT_NUMBER);
    return v->n;
}

使用者应确保类型正确,才调用此 API。我们继续使用断言来保证。

3、单元测试

test.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "leptjson.h"

static int main_ret = 0;         /*main函数的返回值*/
static int test_count = 0;       /*测试计数*/
static int test_pass = 0;        /*测试通过计数*/

/*反斜线代表该行未结束,会串接下一行*/
/*这是一个宏:EXPECT_EQ_BASE(equality, expect, actual, format)
  参数:equality(相等),expect(期望),actual(实际),format(格式)
*/

#define EXPECT_EQ_BASE(equality, expect, actual, format) \

    /*
        大框架是一个do{}while
        先测试计数自加
        再判断相等是否为真,为真:测试通过自加;为假:输出相关信息。并且将main函数的返回值置为1
    */
    do {\
        test_count++;\
        if (equality)\
            test_pass++;\
        else {\
            fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\
            main_ret = 1;\
        }\
    } while(0)

/*判断整数*/
#define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d")
/*判断浮点数*/
#define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g")

/*测试 "null"*/
static void test_parse_null() {
    lept_value v;
    v.type = LEPT_FALSE;
    EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null"));
    EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));
}

/*测试 "true"*/
static void test_parse_true() {
    lept_value v;
    v.type = LEPT_FALSE;
    EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true"));
    EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v));
}
/*测试 "false"*/
static void test_parse_false() {
    lept_value v;
    v.type = LEPT_TRUE;
    EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false"));
    EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v));
}

/*测试expect和json。【这是本次的重点】*/
#define TEST_NUMBER(expect, json)\
    do {\
        /*定义变量 v*/
        lept_value v;\
        /*判断 v这个JSON数据 和 LEPT_PARSE_OK 是否相等*/
        EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\
        /*判断v的数据类型是否是LEPT_NUMBER */
        EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(&v));\
        /*判读v是否等于expect*/
        EXPECT_EQ_DOUBLE(expect, lept_get_number(&v));\
    } while(0)

/*测试数据*/
static void test_parse_number() {
    TEST_NUMBER(0.0, "0");
    TEST_NUMBER(0.0, "-0");
    TEST_NUMBER(0.0, "-0.0");
    TEST_NUMBER(1.0, "1");
    TEST_NUMBER(-1.0, "-1");
    TEST_NUMBER(1.5, "1.5");
    TEST_NUMBER(-1.5, "-1.5");
    TEST_NUMBER(3.1416, "3.1416");
    TEST_NUMBER(1E10, "1E10");
    TEST_NUMBER(1e10, "1e10");
    TEST_NUMBER(1E+10, "1E+10");
    TEST_NUMBER(1E-10, "1E-10");
    TEST_NUMBER(-1E10, "-1E10");
    TEST_NUMBER(-1e10, "-1e10");
    TEST_NUMBER(-1E+10, "-1E+10");
    TEST_NUMBER(-1E-10, "-1E-10");
    TEST_NUMBER(1.234E+10, "1.234E+10");
    TEST_NUMBER(1.234E-10, "1.234E-10");
    TEST_NUMBER(0.0, "1e-10000"); /* must underflow */
}

/*测试error和json*/
#define TEST_ERROR(error, json)\
    do {\
        /*定义变量 v*/
        lept_value v;\
        /*将v的数据类型修改为 LEPT_FALSE*/
        v.type = LEPT_FALSE;\
        /*判断error 和v是否相等*/
        EXPECT_EQ_INT(error, lept_parse(&v, json));\
        /*判断v的数据类型是否是 LEPT_NULL*/
        EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\
    } while(0)

/*测试解析期望值*/
static void test_parse_expect_value() {
    /*测试LEPT_PARSE_EXPECT_VALUE 和"" 是否相等*/
    TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, "");
/*测试LEPT_PARSE_EXPECT_VALUE 和" " 是否相等*/
    TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " ");
}

/*解析非法的数据*/
static void test_parse_invalid_value() {
    /* 解析"nul"是否是非法的数据 */
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul");
     /* 解析"?"是否是非法的数据 */
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?");

/*这些都是非法的数据*/
#if 0
    /* invalid number */
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0");
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1");
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1.");   /* at least one digit after '.' */
    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");
#endif
}
/*解析根不是单数,即根是双数*/
static void test_parse_root_not_singular() {
    TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x");

/*非法的数据。0之后是'.'或者'E'或者'e'*/
#if 0
    /* invalid number */
    TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' , 'E' , 'e' or nothing */
    TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0");
    TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123");
#endif
}
/*解析大数据*/
static void test_parse_number_too_big() {
#if 0
    TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309");
    TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309");
#endif
}

static void test_parse() {
    test_parse_null();
    test_parse_true();
    test_parse_false();
    test_parse_number();
    test_parse_expect_value();
    test_parse_invalid_value();
    test_parse_root_not_singular();
    test_parse_number_too_big();
}

int main() {
    test_parse();
    printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count);
    return main_ret;
}

写一些单元测试。这次我们使用多行的宏的减少重复代码:

【从test.c中摘录一部分】

#define TEST_NUMBER(expect, json)\
    do {\
        lept_value v;\
        EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\
        EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(&v));\
        EXPECT_EQ_DOUBLE(expect, lept_get_number(&v));\
    } while(0)

static void test_parse_number() {
    TEST_NUMBER(0.0, "0");
    TEST_NUMBER(0.0, "-0");
    TEST_NUMBER(0.0, "-0.0");
    TEST_NUMBER(1.0, "1");
    TEST_NUMBER(-1.0, "-1");
    TEST_NUMBER(1.5, "1.5");
    TEST_NUMBER(-1.5, "-1.5");
    TEST_NUMBER(3.1416, "3.1416");
    TEST_NUMBER(1E10, "1E10");
    TEST_NUMBER(1e10, "1e10");
    TEST_NUMBER(1E+10, "1E+10");
    TEST_NUMBER(1E-10, "1E-10");
    TEST_NUMBER(-1E10, "-1E10");
    TEST_NUMBER(-1e10, "-1e10");
    TEST_NUMBER(-1E+10, "-1E+10");
    TEST_NUMBER(-1E-10, "-1E-10");
    TEST_NUMBER(1.234E+10, "1.234E+10");
    TEST_NUMBER(1.234E-10, "1.234E-10");
    TEST_NUMBER(0.0, "1e-10000"); /* must underflow */
}

以上这些都是很基本的测试用例,也可供调试用。大部分情况下,测试案例不能穷举所有可能性。因此,除了加入一些典型的用例,我们也常会使用一些边界值,例如最大值等。练习中会让同学找一些边界值作为用例。

除了这些合法的 JSON,我们也要写一些不合语法的用例:

static void test_parse_invalid_value() {
    /* ... */
    /* invalid number */
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0");
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1");
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1.");   /* at least one digit after '.' */
    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");
}

4、十进制转换至二进制

leptjson.c

#include "leptjson.h"
#include <assert.h>  /* assert() */
#include <stdlib.h>  /* NULL, strtod() */
/*判断指针c的第一个位置是否是 ch,并且移动指针*/
#define EXPECT(c, ch)       do { assert(*c->json == (ch)); c->json++; } while(0)

/*封装JSON的结构体*/
typedef struct {
    const char* json;
}lept_context;

/* 轻量解析空白,空白有4中类型
   空格、制表符、换行符、回车符
   ws = *(%x20 / %x09 / %x0A / %x0D) */
static void lept_parse_whitespace(lept_context* c) {
    const char *p = c->json;
    while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')
        p++;
    c->json = p;
}
/*解析"true"*/
static int lept_parse_true(lept_context* c, lept_value* v) {
    EXPECT(c, 't');
    if (c->json[0] != 'r' || c->json[1] != 'u' || c->json[2] != 'e')
        return LEPT_PARSE_INVALID_VALUE;
    c->json += 3;
    v->type = LEPT_TRUE;
    return LEPT_PARSE_OK;
}
/*解析"false"*/
static int lept_parse_false(lept_context* c, lept_value* v) {
    EXPECT(c, 'f');
    if (c->json[0] != 'a' || c->json[1] != 'l' || c->json[2] != 's' || c->json[3] != 'e')
        return LEPT_PARSE_INVALID_VALUE;
    c->json += 4;
    v->type = LEPT_FALSE;
    return LEPT_PARSE_OK;
}
/*解析"null"*/
static int lept_parse_null(lept_context* c, lept_value* v) {
    EXPECT(c, 'n');
    if (c->json[0] != 'u' || c->json[1] != 'l' || c->json[2] != 'l')
        return LEPT_PARSE_INVALID_VALUE;
    c->json += 3;
    v->type = LEPT_NULL;
    return LEPT_PARSE_OK;
}
/*解析 数字   【这是本次的重点】*/
static int lept_parse_number(lept_context* c, lept_value* v) {
    char* end;
    /* \TODO validate number */
    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;
}

/*
    轻量解析数据函数
    传入:封装的JSON数据结构体 和 JSON数据结构
*/
static int lept_parse_value(lept_context* c, lept_value* v) {
    switch (*c->json) {
        case 't':  return lept_parse_true(c, v);
        case 'f':  return lept_parse_false(c, v);
        case 'n':  return lept_parse_null(c, v);
        default:   return lept_parse_number(c, v);
        case '\0': return LEPT_PARSE_EXPECT_VALUE;
    }
}
/*修正关于 LEPT_PARSE_ROOT_NOT_SINGULAR 的单元测试,若 json 在一个值之后,空白之后还有其它字符,则要返回 LEPT_PARSE_ROOT_NOT_SINGULAR*/
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;
}

/*获取类型 函数的实现*/
lept_type lept_get_type(const lept_value* v) {
    assert(v != NULL);
    return v->type;
}
/*仅当 `type == LEPT_NUMBER` 时,`n` 才表示 JSON 数字的数值*/
double lept_get_number(const lept_value* v) {
    assert(v != NULL && v->type == LEPT_NUMBER);
    return v->n;
}

我们需要把十进制的数字转换成二进制的 double。这并不是容易的事情 。为了简单起见,leptjson 将使用标准库的 strtod() 来进行转换。strtod() 可转换 JSON 所要求的格式,但问题是,一些 JSON 不容许的格式,strtod() 也可转换,所以我们需要自行做格式校验。

#include <stdlib.h>  /* NULL, strtod() */

static int lept_parse_number(lept_context* c, lept_value* v) {
    char* end;
    /* \TODO validate number */
    v->n = strtod(c->json, &end);
    if (c->json == end)
        return LEPT_PARSE_INVALID_VALUE;     /*返回解析非法数据*/
    c->json = end;
    v->type = LEPT_NUMBER;                   /*将v的type置为LEPT_NUMBER*/
    return LEPT_PARSE_OK;                    /*返回解析OK*/
}

加入了 number 后,value 的语法变成:

value = null / false / true / number

记得在第一单元中,我们说可以用一个字符就能得知 value 是什么类型,有 11 个字符可判断 number:

  • 0-9/- ➔ number

但是,由于我们在 lept_parse_number() 内部将会校验输入是否正确的值,我们可以简单地把余下的情况都交给 lept_parse_number()

static int lept_parse_value(lept_context* c, lept_value* v) {
    switch (*c->json) {
        case 't':  return lept_parse_true(c, v);
        case 'f':  return lept_parse_false(c, v);
        case 'n':  return lept_parse_null(c, v);
        default:   return lept_parse_number(c, v);
        case '\0': return LEPT_PARSE_EXPECT_VALUE;
    }
}

5、总结与练习

(1)重构合并 lept_parse_null()lept_parse_false()lept_parse_truelept_parse_literal()

在leptjson.c中

由于 true / false / null 的字符数量不一样,这个答案以 for 循环作比较,直至 '\0'

static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) {
    size_t i;                           /*注意在 C 语言中,数组长度、索引值最好使用 `size_t` 类型,而不是 `int` 或 `unsigned`。*/
    EXPECT(c, literal[0]);
    for (i = 0; literal[i + 1]; i++)    /*到 '\0' 结束*/
        if (c->json[i] != literal[i + 1])
            return LEPT_PARSE_INVALID_VALUE;
    c->json += i;
    v->type = type;
    return LEPT_PARSE_OK;
}

static int lept_parse_value(lept_context* c, lept_value* v) {
    switch (*c->json) {
        case 't':  return lept_parse_literal(c, v, "true", LEPT_TRUE);
        case 'f':  return lept_parse_literal(c, v, "false", LEPT_FALSE);
        case 'n':  return lept_parse_literal(c, v, "null", LEPT_NULL);
        /* ... */
    }
}

(2)加入 维基百科双精度浮点数 的一些边界值至单元测试,如 min subnormal positive double、max double 等。

TEST_NUMBER(1.0000000000000002, "1.0000000000000002"); /* the smallest number > 1 */
TEST_NUMBER( 4.9406564584124654e-324, "4.9406564584124654e-324"); /* minimum denormal */
TEST_NUMBER(-4.9406564584124654e-324, "-4.9406564584124654e-324");
TEST_NUMBER( 2.2250738585072009e-308, "2.2250738585072009e-308");  /* Max subnormal double */
TEST_NUMBER(-2.2250738585072009e-308, "-2.2250738585072009e-308");
TEST_NUMBER( 2.2250738585072014e-308, "2.2250738585072014e-308");  /* Min normal positive double */
TEST_NUMBER(-2.2250738585072014e-308, "-2.2250738585072014e-308");
TEST_NUMBER( 1.7976931348623157e+308, "1.7976931348623157e+308");  /* Max double */
TEST_NUMBER(-1.7976931348623157e+308, "-1.7976931348623157e+308");

这里运行,有个bug,一直没有解决

(3)去掉 test_parse_invalid_value()test_parse_root_not_singular 中的 #if 0 ... #endif,执行测试,证实测试失败。按 JSON number 的语法在 lept_parse_number() 校验,不符合标准的程况返回 LEPT_PARSE_INVALID_VALUE 错误码。

 

 

 

(4)去掉 test_parse_number_too_big 中的 #if 0 ... #endif,执行测试,证实测试失败。仔细阅读 strtod(),看看怎样从返回值得知数值是否过大,以返回 LEPT_PARSE_NUMBER_TOO_BIG 错误码。(提示:这里需要 #include 额外两个标准库头文件。)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值