JSON库教程 tutorial02 学习笔记


前言

本文紧接着tutorial01的学习,旨在记录学习过程。

看了第二章之后才发现难的其实就是第一步,第一步是搭建框架,最重要的是要理解框架各个部分的意思,有了tutorial01做基础,第二章的学习理解起来也是比较快速。

本章的目标是实现数字的解析,在第一章的时候实现了True、False、Null的表达,接下来要想想怎么实现数字,来看看JSON的数字语法:

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)。


接下来来看具体的代码。

一、LEPTJSON.h

#ifndef LEPTJSON_H__
#define LEPTJSON_H__

typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type;

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
};

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__ */

  • 数据结构中添加了double n用来存储数据
  • 返回值增加了LEPT_PARSE_NUMBER_TOO_BIG表示过大的数
  • 多了一个API函数用于获取数字的值

总体上改动不是很大,还是比较清晰的。

二、LEPTJSON.c

还是先贴上代码

#include "leptjson.h"
#include <assert.h>  /* assert() */
#include <math.h>
#include <errno.h>
#include <stdlib.h>  /* NULL, strtod() */

#define EXPECT(c, ch)       do { assert(*c->json == (ch)); c->json++; } while(0)

#define ISDIGIT(ch)         ((ch) >= '0' && (ch) <= '9')
#define ISDIGIT1TO9(ch)     ((ch) >= '1' && (ch) <= '9')

typedef struct {
    const char* json;
}lept_context;

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;
}

//作业1 重构合并 lept_parse_null()、lept_parse_false()、lept_parse_true() 为 lept_parse_literal()
static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) {
    size_t i;
    /*跳过进入的第一个字符*/
    EXPECT(c, literal[0]);
    /* [i + 1] 是为了和跳过一个字符的c->json[i]对应起来
      for参数内的condition意为:当literal[i + 1]为真(即未超过数组上限)时,继续执行循环
    */
    for (i = 0; literal[i + 1]; i++)
        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_number(lept_context* c, lept_value* v) {
    const char* p = c->json;
    
    //作业3:校验数字
    /*负号*/
    /*若第一个为负号,跳过*/
    if (*p == '-')
        p++;

    /*整数*/
    /*若为0,跳过*/
    if (*p == '0')
        p++;
    else {
        /*若不为数字*/
        if(!ISDIGIT(*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++);
    }
    /* \TODO validate number */
    errno = 0;
    v->n = strtod(c->json, NULL);  /*strtod()函数,用于把十进制的数字转换成二进制的double*/
    if (errno == ERANGE && v->n == HUGE_VAL) return LEPT_PARSE_NUMBER_TOO_BIG;
#if 0
    if (isinf(v->n))
        return LEPT_PARSE_NUMBER_TOO_BIG;
#endif
    c->json = p;
    v->type = LEPT_NUMBER;
    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);
        default:   return lept_parse_number(c, v);   /*lept_parse_number()会校验值,因此直接返回lept_parse_number()即可*/
        case '\0': return LEPT_PARSE_EXPECT_VALUE;
    }
}

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;
}

double lept_get_number(const lept_value* v) {
    //不为空且类型必须为数字类型才可以获取n
    assert(v != NULL && v->type == LEPT_NUMBER);
    return v->n;
}

东西有点多,一个个来,先从lept_get_number()函数讲起

double lept_get_number(const lept_value* v) {
    //不为空且类型必须为数字类型才可以获取n
    assert(v != NULL && v->type == LEPT_NUMBER);
    return v->n;
}
  • 字面意思,仍用assert()来保证输入的是数字
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);
        default:   return lept_parse_number(c, v);   /*lept_parse_number()会校验值,因此直接返回lept_parse_number()即可*/
        case '\0': return LEPT_PARSE_EXPECT_VALUE;
    }
}
  • lept_parse_value()函数的修改地方在于两点,一个是case ‘t’ ‘f’ 'n’统一用了lept_parse_literal()函数,这个放在最后讲,不是本节课的重点。
  • 一个是default下变成了lept_parse_number()函数,这也是本节课的重点函数,也就是实现解析数字的函数。因为在解析数字的时候,会同时判断是否为非法字符,因此默认直接返回解析数字函数即可。

接下来看看解析数字函数的实现(未完成作业3的版本):

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;
}
  • 首先注意到用了一个strtod()函数。我们需要把十进制的数字转换成二进制的 double。这并不是容易的事情。为了简单起见,leptjson 将使用标准库的 strtod() 来进行转换。strtod() 可转换 JSON 所要求的格式,但问题是,一些 JSON 不容许的格式,strtod() 也可转换,所以我们需要自行做格式校验(作业3)。

由标准库中 strtod() 可知其相关定义

float       strtof( const char *restrict str, char **restrict str_end );   (since C99)
double      strtod( const char          *str, char          **str_end );   (until C99)
double      strtod( const char *restrict str, char **restrict str_end );   (since C99)
long double strtold( const char *restrict str, char **restrict str_end );  (since C99)

end的含义在于表示输出从某个字符开始无法转换为double的字符串,例如:

123.0ABC48.25
ABC456QD

此时end存储的字符串为

ABC48.25  //123.0被转换成double类型存储在左值中
ABC456QD
  • 因此若 c->json == end 说明字符串根本没有被转化,因而这个函数是非法的,他不是一个合法的数字。
  • 若能被完全转化为double,end会等于0,因此让 c->json = end 相当于跳过了这串数字,之后只要设置格式以及返回OK就万事大吉了。
  • 但此时有个很严重的问题,我们看test.c中设置了怎么样的测试先:
static void test_parse_invalid_value() {
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul");
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?");

#if 1
    /* 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
}
  • 我们会发现新加了一些非法的数字,但经过测试,会发现,这些数字在 strtod() 函数均会通过,即本应该是非法的,在strtod()下却是可以被识别的,因此我们需要自行做格式检验,也就是作业3,下面给出作业3修改版:

这也是本章中最难的部分

static int lept_parse_number(lept_context* c, lept_value* v) {
    const char* p = c->json;
    
    //作业3:校验数字
    /*负号*/
    /*若第一个为负号,跳过*/
    if (*p == '-')
        p++;

    /*整数*/
    /*若为0,跳过*/
    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++);
    }
    /* \TODO validate number */
    errno = 0;
    v->n = strtod(c->json, NULL);  /*strtod()函数,用于把十进制的数字转换成二进制的double*/
    if (errno == ERANGE && (v->n == HUGE_VAL || v->n == -HUGE_VAL))
    	return LEPT_PARSE_NUMBER_TOO_BIG;
    c->json = p;
    v->type = LEPT_NUMBER;
    return LEPT_PARSE_OK;
}
  • 由JSON的数字语法可知,第一个可能为负号,因此若为负号则跳过。
  • 之后若第一个为0,跳过(因为0只能有一个,后面只能接小数点),否则必须出现的是1-9的数字,而不能是其他字符或者0,否则非法。然后跳过所有的数字
  • 跳过小数点,后面必然为数字否则非法,之后跳过所有数字
  • e或者E跳过,+或者-跳过,之后必然为数字否则非法,之后跳过所有数字
  • 之后我们要引用 <errno.h> 这个头文件从而可以用 errno 这个参数。 errno 不同的值代表不同含义,可以通过查看该值推测出错的原因。
    此处令 errno == ERANGE 查宏定义可知
#define ERANGE 34 / Math result not representable /

表示数字结果无法表示,结合后续的判断n的值是否为正无穷和负无穷可知,是用来防止用户输入过大的数值的,若是输入了过大的数值,则会返回 LEPT_PARSE_NUMBER_TOO_BIG

剩下的并未做太大改动相信读起来应该能理解

三、test.c

惯例先贴代码:

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

static int main_ret = 0;
static int test_count = 0;
static int test_pass = 0;

#define EXPECT_EQ_BASE(equality, expect, actual, format) \
    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")

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));
}

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));
}

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));
}

#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 */

    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");
}

#define TEST_ERROR(error, json)\
    do {\
        lept_value v;\
        v.type = LEPT_FALSE;\
        EXPECT_EQ_INT(error, lept_parse(&v, json));\
        EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\
    } while(0)

static void test_parse_expect_value() {
    TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, "");
    TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " ");
}

static void test_parse_invalid_value() {
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul");
    TEST_ERROR(LEPT_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");
}

static void test_parse_root_not_singular() {
    TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x");

    /* invalid number */
    TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */
    TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0");
    TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123");
}

static void test_parse_number_too_big() {
    TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309");
    TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309");
}

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;
}

  • 最显而易见的是添加了许多数字的测试,并且在作业2中还额外添加了一堆边界值数字(这部分不是很看得懂):
    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");
  • 值得注意的是新定义的宏 TEST_ERROR() ,目的是重构代码,简化类似的代码,原理和之前一致:
#define TEST_ERROR(error, json)\
    do {\
        lept_value v;\
        v.type = LEPT_FALSE;\
        EXPECT_EQ_INT(error, lept_parse(&v, json));\
        EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\
    } while(0)

四、重构代码

说回之前的 lept_parse_literal() ,其实就是将三个函数写在了一起,稍微看看他的实现:

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])
            return LEPT_PARSE_INVALID_VALUE;
    c->json += i;
    v->type = type;
    return LEPT_PARSE_OK;
}
  • 转入函数后首先跳过第一个字母,因此是 EXPECT(c, literal[0])
  • 注意在 C 语言中,数组长度、索引值最好使用 size_t 类型,而不是 intunsigned
  • 由于一开始跳过了第一个字母,所以之后的对比是和 i + 1 下标的进行对比,如 rue 和 true 进行对比

总结

  • 总体来讲第二章的难度比第一章要小,原因在于是在原有的基础架构上进行的修改与添加,只要理解了架构的含义,对于第二章的理解就只是对几个函数的理解了
  • 本次的难点在于解析数字的函数,自己思考的时候还是有思路的,但最终发现没有答案完美,并且对一部分测试通过不了。后面发现还是答案这种写法比较优秀。

参考文章

  1. https://github.com/miloyip/json-tutorial/blob/master/tutorial02/tutorial02.md
  2. https://blog.csdn.net/tonglin12138/article/details/86545357?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522170981655616800226592572%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=170981655616800226592572&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-86545357-null-null.142^v99^pc_search_result_base6&utm_term=errno
  • 25
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值