开源项目cJSON具体实现1(NULL 与 Boolean的解析)

1. 实现 NULL 与 Boolean的解析。

1.1 JSON的语法规则与解释。

先说说关于 JSON NULL 与 JSON Boolean 的语法:

/*
解释:
	当中 %xhh 表示以 16 进制表示的字符,/ 是多选一,* 是零或多个,( ) 用于分组。
	
	第一行的意思是,JSON 文本由 3 部分组成,首先是空白(whitespace),接着是一个值,最后是空白。
	第二行的意思是,所谓空白,是由零或多个空格符(space U+0020)、制表符(tab U+0009)、换行符(LF U+000A)、回车符(CR U+000D)所组成。
	第三行是说,我们现时的值只可以是 null、false 或 true,它们分别有对应的字面值(literal)。
*/
JSON-text = ws value ws
ws = *(%x20 / %x09 / %x0A / %x0D)
value = null / false / true 
null  = "null"
false = "false"
true  = "true"
1.2 设计头文件

声明一个枚举值 lept_type 表示JSON的类型。

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

JSON 是一个树形结构,设计 节点使用 结构体类型 来表示。 因为现在我们只需要实现 null, true 和 false 的解析,所以目前的 结构体只需要存储一个 JSON类型。

typedef struct {
	lept_type type;
}lept_value;

API设计

/*
函数目的:解析JSON ,如果输入的 JSON文本 不合法,要产生对应的错误码,方便使用者追查问题。所以这个函数需要有返回值,并且要根据返回值来判断不同的情况,为此再设计一个枚举类型来表示不同返回值的情况。
参数:1. lept_value* v :传入存储解析后JSON树形结构的根节点指针
 	2. const char* json :传入的 JSON 文本
返回值:int   
*/
int lept_parse(lept_value* v, const char* json);

enum {
    LEPT_PARSE_OK = 0,   //无错误会返回

	//错误码
    LEPT_PARSE_EXPECT_VALUE,  //JSON 只含有空白
    LEPT_PARSE_INVALID_VALUE,  //若值不是true、false、null
   LEPT_PARSE_ROOT_NOT_SINGULAR   //一个值之后,在空白之后还有其他字符
};

/* 
函数目的: 获得JSON的类型
参数:1. lept_value* v :存储JSON树状结构的根节点指针 
返回值:lept_type 表示 JSON的类型
*/
lept_type lept_get_type(const lept_value* v);
1.3 TDD设计理念

按照TDD的设计思想,我们应该先写测试,再实现功能。

拓展:测试驱动开发(test-driven development, TDD),它的主要循环步骤是:
1.加入一个测试。
2.运行所有测试,新的测试应该会失败。
3.编写实现代码。
4.运行所有测试,若有测试失败回到3。
5.重构代码。
6.回到 1。

首先针对我们今天要实现的函数,来写一个极简的单元测试框架。

#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;   //用来记录测试用例通过的数字

/* 这两个宏的作用:如果expect(预期值) != actual(实际值), 便会打印出现错误信息。*/
#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")

/*
函数目的:1. 测试解析null
步骤:
	1. 新建一个lept_value的对象,并初始化
	2. 用宏EXPECT_EQ_INT做测试,判断当json文本为null时,lept_parse函数是否能正常解析。
	3. 测试当结点保存的是null时,lept_get_type的返回值与LEPT_NULL是否相同。
*/
static void test_parse_null() {
	lept_value v;
	v.type = LEPT_FALSE;  
    EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null"));//测试lept_parse解析null
    EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));//测试 lept_get_type = LEPT_NULL
}

/* 函数目的:调用测试用例函数 */
static void test_parse() {
    test_parse_null();
}

int main() {
    test_parse();
    printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); //打印测试通过率
    return main_ret;
}

关于上面的代码的拓展说明:

  • fprintf(stderr, "%s:%d: expect: " format " actual: " format “\n”, FILE, LINE, expect, actual); 的意思是:
    1. fprintf是C/C++中的一个格式化库函数,位于头文件中,其作用是格式化输出到一个流文件中
    2. stderr 标准错误
    3. FILELINEDATA,TIME 是C / C++编译器内置宏,这些宏定义可以帮助我们完成跨平台的源码编写,也可以输出有用的调试信息。
    3.1 FILE:在源文件中插入当前源文件路径及文件名;
    3.2 LINE:在源代码中插入当前源代码行号;
  • 为什么要用的do {… } while (0)?
    如果宏里有多过一个语句(statement),就需要用 do { …} while (0) 包裹成单个语句
1.4 实现解析器

根据 API 与 单元测试 ,我们来实现解析器

#include "leptjson.h"
#include <assert.h>  /* assert() */
#include <stdlib.h>  /* NULL */

/*为了减少解析函数的参数,把这些数据都放进一个lept_context 结构体中*/
typedef struct {
    const char* json;
}lept_context;

/* 
函数目的:解析杂乱无章的 JSON 文本
参数:1. lept_value* v :存储JSON树状结构的根节点指针 2. const char* json :传入的 JSON 文本
返回值:int 数值,不同的数值表示了解析过程中的不同情况

实现思路:
1. 首先查看JSON文本的格式:JSON-text = ws value ws。
2. 解析前我们得先处理掉前面的ws,
3. 拿到value后,再根据value 的类型进行对应的处理
4. 处理完后还要处理最后的 ws ,最后的空白处理不能像第一个那么简单就处理了,为什么这么说呢?假设说我们接受到的JSON-text = null x,首先这段text满足了JSON-text,但是在最后有又多余了个x,这样就不是正确的JSON-text格式,所以我们得确定的是我们处理的JSON-text的最后一个空白后面接的是'\0'
*/
int lept_parse(lept_value* v, const char* json) {
	assert(v != NULL);
	
	lept_context c;
	int ret;
	c.json = json;
	
	v->type = LEPT_NULL;//lept_parse() 若失败,会把 v 设为 null 类型,所以这里先把它设为 null,最终让 lept_parse_value() 写入解析出来的类型值。	
	
	lept_parse_whitespace(&c);//处理第一个空白
	
	if (LEPT_PARSE_OK == (ret = lept_parse_value(&c,v))) //lept_parse_value是value对应的处理函数
	{
		lept_parse_whitespace(&c);//处理最后的空白
		
		if ('\0' != *(c.json))
		{
			ret = LEPT_PARSE_ROOT_NOT_SINGULAR;//若一个值之后,在空白之后还有其他字符,返回错误码
		}
	}
	return ret;
}

lept_type lept_get_type(const lept_value* v) {
    assert(v != NULL);
    return v->type;
}

根据上面的代码,实现两个函数 lept_parse_whitespace 与 lept_parse_value:

/* 函数目的:处理空白符 */
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;
}

/*
函数目的: 对value进行解析
思路:
1. 首先我们得先判断出这个value是做什么类型,如何判断?根据第一个字符就可以,如果是n,就代表是null;t ➔ true;f ➔ false;" ➔  string等等
2. 对不同的类型进行不同的处理
*/
static int lept_parse_value(lept_context* c, lept_value* v) {
    switch (*c->json) {
        case 'n':  return lept_parse_null(c, v);
		case '\0': return LEPT_PARSE_EXPECT_VALUE;
		default:   return LEPT_PARSE_INVALID_VALUE;
    }
}

根据上面的代码,实现lept_parse_null

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

/* 
函数目的:null的解析函数
思路:
1. 进一步判断传入的文本是不是null
2. 是,设置接收数据结点的类型为LEPT_NULL,并返回LEPT_PARSE_OK
3. 不是,返回错误码
4. 记得 将传入的文本向后移动4位,其实应该是判断一个字符就移动一个字符,方便后续处理。
*/
static int lept_parse_null(lept_context* c, lept_value* v) {
    EXPECT(c, 'n'); //及得这里有一个json++
    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;
}
1.5 照猫画虎–上面实现了null的情况,接下来实现tree与false

首先同样是先写测试函数

/*
函数目的:测试解析true
测试思路同 test_parse_null
*/
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));
}

/*
函数目的:测试JSON只含有空白字符
*/
static void test_parse_expect_value() {
    lept_value v;	
	v.type = LEPT_FALSE;
    EXPECT_EQ_INT(LEPT_PARSE_EXPECT_VALUE, lept_parse(&v, ""));
    EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));

    v.type = LEPT_FALSE;
    EXPECT_EQ_INT(LEPT_PARSE_EXPECT_VALUE, lept_parse(&v, " "));
    EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));
}

/* 函数目的:测试JSON文本 不是三种字面值(null,false,true)的情况 */
static void test_parse_invalid_value() {
    lept_value v;

    v.type = LEPT_FALSE;
    EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "nul"));
    EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));

	v.type = LEPT_FALSE;
	EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "fal"));
	EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));

	v.type = LEPT_FALSE;
	EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "?"));
	EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));
}

/* 函数目的:测试JSON文本在最后空白之后还有其他字符 */
static void test_parse_root_not_singular() {
    lept_value v;
    v.type = LEPT_FALSE;
    EXPECT_EQ_INT(LEPT_PARSE_ROOT_NOT_SINGULAR, lept_parse(&v, "null x"));
	EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));
}

//记得调用
static void test_parse() {
    test_parse_null();
	test_parse_true();
	test_parse_false();
    test_parse_expect_value();
    test_parse_invalid_value();
    test_parse_root_not_singular();
}

实现true 与 false 的解析器

/*若value = 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;
}

/*若value = 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;
}

static int lept_parse_value(lept_context* c, lept_value* v) {
    switch (*c->json) {
        case 'n':  return lept_parse_null(c, v);//null
		case 'f':  return lept_parse_false(c, v);//false
		case 't':  return lept_parse_true(c, v);//true
       	case '\0': return LEPT_PARSE_EXPECT_VALUE;//空白
		default:   return LEPT_PARSE_INVALID_VALUE;//若值不是那三种字面值
    }
}
1.6 思考

其实很明显就可以看出,上面的代码有些问题: 重复的代码太多,对付重复的代码太多。就要用到重构。重构是一个这样的过程:在不改变代码外在行为的情况下,对代码作出修改,以改进程序的内部结构。

在 TDD 的过程中,我们的目标是编写代码去通过测试。但由于这个目标的引导性太强,我们可能会忽略正确性以外的软件品质。在通过测试之后,代码的正确性得以保证,我们就应该审视现时的代码,看看有没有地方可以改进,而同时能维持测试顺利通过。我们可以安心地做各种修改,因为我们有单元测试,可以判断代码在修改后是否影响原来的行为。

怎么处理呢?可以宏简化,比如我们可以将测试函数中的test_parse_null() 这样写:

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

static void test_parse_null() {
	TEST_ERROR(LEPT_PARSE_OK, "null", LEPT_NULL);
}

再比如,我们在解析器中对于true、false、null的解析代码十分相似,所以我们可以把他们合到一个函数中:

//true、false、null的解析代码
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;
}

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);
       
 /* ... */
    }
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值