cJSON开源项目详细解剖

Posted 林夕07

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了cJSON开源项目详细解剖相关的知识,希望对你有一定的参考价值。

JSON

JSON介绍

百度百科:JSON(javascript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。

JSON语法

JSON是一个标记符的序列。这套标记符包含六个构造字符、字符串、数字和三个字面名。JSON是一个序列化的对象或数组。

数组和对象(大括号包含的)有以key:value格式还可以有value格式的,
如下:
key:value格式

{
	"name"	: "John Doe", 
	"age"	: 	18, 
	"address": {
				"country" 	: "china",
				 "zip-code"	: "10000"
				}
}

value格式

[3, 1, 4, 1, 5, 9, 2, 6]

混合格式

[1, 2, "3", {
				"a": 4
			}
]

CJSON

源码下载及介绍

顾名思义就是用C语言实现JSON格式的解析器,可以将文本构造成JSON格式数据,也可以将JSON格式数据解析成文本。
下载地址:CJSON源码
下载好之后这是一个zip的压缩包,需要解压。下图就是解压后的所有文件
在这里插入图片描述
LICENSE文件和README文件是关于版权的介绍,建议带上就行。
cJSON.ccJSON.h是源码文件,test.c是一个测试文件,tests文件夹里面放一些JSON格式的数据以便测试使用。

CJSON思想

结构体

CJSON是使用双向链表存储数据,访问类型与树结构,所以我们先来了解这个结构体。

typedef struct cJSON
	{
		struct cJSON* next;	// 向后链表指针
		struct cJSON* prev;	// 向前链表指针
		struct cJSON* child;// 对象或者数组的孩子节点指针

		
		int type;					// value的类型

		char* valuestring;			// 字符串值
		int valueint;				// 整数值
		double valuedouble;			// 浮点数值 

		char* string;				// 存放key
	} cJSON;

type是值(value)的类型,一共有7种取值:
分别是:False,Ture,NULL,Number,String,Array,Object。

  1. Number类型,则valueint或valuedouble中存储着值。
  2. int类型,则valueint中存储着值
  3. double类型,则valuedouble中存储着值。
  4. String类型,则valuestring中存储着值。

画出CJSON结构

在这里插入图片描述

现在可以试着将上图的结构画出来。嵌套用child连接,兄弟关系用链表连接。
在这里插入图片描述

解析失败的措施

static const char* ep;
将会定位在解析失败的字符上,并将不能解析的字符串输出

函数解析

cJSON_GetErrorPtr

/*
作  用:字符串解析失败函数
返回值:返回全局变量ep
*/
const char* cJSON_GetErrorPtr(void)
{
	return ep;
}

cJSON_strcasecmp

/*
作	用:用来比较参数s1和s2字符串,比较时会自动忽略大小写的差异
参	数:s1、s2是需要比较字符串
返回值:0表示相同  其他数值表示不相同
*/
static int cJSON_strcasecmp(const char* s1, const char* s2)
{
	if (NULL == s1 && NULL == s2)						// s1均为null就返回0
	{
		return 0;
	}

	if (NULL == s1 || NULL == s2)						// 只要有一方为null就返回1
	{
		return 1;
	}

	for (; tolower(*s1) == tolower(*s2); ++s1, ++s2)	// tolower()大写转小写函数
	{
		if (*s1 == '\\0')								// 发现全部匹配成功就返回0
		{
			return 0;
		}
	}
	return tolower(*(const unsigned char*)s1) - tolower(*(const unsigned char*)s2);
}

cJSON_strdup

/*
作	用:为str字符串在堆区开辟一块空间
参	数:字符串
返回值:堆区地址
*/
static char* cJSON_strdup(const char* str)
{
	size_t len = 0;										// size_t等价于unsigned int
	char* copy = NULL;									// 保存堆区开辟的地址

	len = strlen(str) + 1;								// 计算字符串长度 最后需要加个'\\0' 所以长度+1
	copy = (char*)cJSON_malloc(len);					// 为字符串申请空间
	if (NULL == copy)									// 空间申请失败
	{
		return 0;
	}

	memcpy(copy, str, len);								// 将str复制到copy中,长度为len

	return copy;
}

cJSON_InitHooks

/* 初始化钩子 */
void cJSON_InitHooks(cJSON_Hooks* hooks)
{
	if (NULL == hooks)
	{
		cJSON_malloc = malloc;
		cJSON_free = free;
		return;
	}

	cJSON_malloc = (hooks->malloc_fn) != NULL ? hooks->malloc_fn : malloc;
	cJSON_free = (hooks->free_fn) != NULL ? hooks->free_fn : free;
}

cJSON_New_Item

/*
作	用:为cJSON在堆区申请一块空间
返回值:堆区地址
*/
static cJSON* cJSON_New_Item(void)
{
	cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON));	// 申请一个结构体的空间
	
	if (NULL == node)									// 申请空间失败
	{
		return NULL;
	}
	
	memset(node, 0, sizeof(cJSON));						// 初始化堆区开辟的内存
	return node;
}

cJSON_Delete

/*
作 用:删除一个cJSON结构
参 数:结构体地址
*/
void cJSON_Delete(cJSON* c)
{
	cJSON* next = NULL;

	while (NULL != c)
	{
		next = c->next;									// 指针向后移

		// 当前兄弟节点是最后一个节点且有孩子
		if (0 == (c->type & cJSON_IsReference) && NULL != c->child)
		{
			cJSON_Delete(c->child);
		}

		// 当前兄弟节点是最后一个且没有孩子,若有value为字符串就回收
		if (0 == (c->type & cJSON_IsReference) && NULL != c->valuestring)
		{
			cJSON_free(c->valuestring);
		}

		// 当前兄弟节点是最后一个且没有孩子,若有key也会受
		if (0 == (c->type & cJSON_StringIsConst) && NULL != c->string)
		{
			cJSON_free(c->string);
		}

		cJSON_free(c);									// 回收根节点
		c = NULL;
	}
}

parse_number

/*
作	用:将输入的字符串解析成数字(正数不需要+号,直接是数字,只有负数才有符号-)
参	数:item-结构体指针   num-字符串指针
返回值:字符串指针
*/
static const char* parse_number(cJSON* item, const char* num)
{	// 
	double n = 0;										// 保存数字
	double sign = 1;									// 数字符号的标志  1表示正数  -1表示负数
	double scale = 0;									// 用于有小数的数字,先不管小数点,记录扩大了多少倍
	int subscale = 0;									// 指数部分的数值值
	int signsubscale = 1;								// 指数部分的符号位

	if (*num == '-')									// 负数
	{
		sign = -1;										// 设置标记
		num++;											// 指针后移
	}

	if (*num == '0')									// 是0
	{
		num++;
	}

	/* 整数部分 */
	if (*num >= '1' && *num <= '9')						// 数字不能从0开始
	{
		do
		{
			n = (n * 10.0) + (*num - '0');
			num++;										// 指针后移
		} while (*num >= '0' && *num <= '9');			// 之后的数字范围就是0-9
	}

	/* 小数部分 */
	if (*num == '.' && num[1] >= '0' && num[1] <= '9')	// 当前字符为"."且下一个字符为数字才有效
	{
		num++;											// 跳过 小数点
		do
		{
			n = (n * 10.0) + (*num - '0');
			num++;
			scale++;									// 记录扩大了多少倍
		} while (*num >= '0' && *num <= '9');
	}

	/* 指数部分 */
	if (*num == 'e' || *num == 'E')
	{
		num++;											// 指针跳过指数

		if (*num == '+')								// 指数位的符号位
		{
			num++;
		}
		else if (*num == '-')
		{
			signsubscale = -1;							// 修改指数位的符号位
			num++;
		}

		while (*num >= '0' && *num <= '9')				// 指数部分的数字位  可以从0开始
		{
			subscale = (subscale * 10) + (*num++ - '0');
		}
	}

	// 将之前扩大的再缩回来 公式:数值符号位 * 数值位 * 10^(指数符号位 * 指数数值位 - 扩大倍数)
	n = sign * n * pow(10.0, (subscale * signsubscale - scale));

	item->valuedouble = n;								// 将该值存入double类型中
	item->valueint = (int)n;							// 将该值存入int类型中
	item->type = cJSON_Number;							// 将type类型设置为数字
	return num;
}

pow2gt

/* 作用:返回比x大的最小的2的N次方数 */
static int pow2gt(int x)
{
	--x;
	x |= x >> 1;
	x |= x >> 2;
	x |= x >> 4;
	x |= x >> 8;
	x |= x >> 16;

	return x + 1;
}

ensure

/*
作	用:判断当前偏移量下比较needed长度的字符是否越界并作出保护措施
参	数:p-输出缓冲结构体指针  needed-比较的长度
返回值:返回比较的首地址
*/
static char* ensure(printbuffer* p, int needed)
{
	char* newbuffer = NULL;
	int newsize = 0;

	if (NULL == p || NULL == p->buffer)		   // 结构体为空或者结构体缓冲区为空
	{
		return NULL;
	}

	needed += p->offset;						// 计算原本偏移量 + 新的比较长度

	if (needed <= p->length)					// 判断偏移量+比较长度是否小于字符串长度
	{
		return p->buffer + p->offset;			// 返回初始地址 + 偏移量
	}

	newsize = pow2gt(needed);					// 返回比x大的最小的2的N次方数
	newbuffer = (char*)cJSON_malloc(newsize);
	if (NULL == newbuffer)						// 空间申请失败
	{
		cJSON_free(p->buffer);
		p->length = 0;
		p->buffer = 0;
		return NULL;
	}

	memcpy(newbuffer, p->buffer, p->length);	// 将p->buffer拷贝到newbuffer,长度为参数三

	cJSON_free(p->buffer);						// 回收掉之前用于保存字符串的空间
	p->length = newsize;						// 更新输出缓冲区结构体
	p->buffer = newbuffer;						// 更新输出缓冲区结构体
	return newbuffer + p->offset;
}

update

/*
作	用:获取缓冲区的字符串长度(不是缓冲区大小,缓冲区大于等于字符串长度)
参	数:p-输出缓冲结构体指针
返回值:长度
*/
static int update(printbuffer* p)
{
	char* str = NULL;

	if (NULL == p || NULL == p->buffer)		   // 结构体为空或者结构体缓冲区为空
	{
		return 0;
	}

	str = p->buffer + p->offset;	
	return p->offset + strlen(str);			   // 偏移量 + 后半段长度
}

print_number

/*
作	用:将给的数字类型结构体转换为字符串存储到p缓冲区中,若p为null就申请空间
参	数:item-给的数字类型结构体  p-缓冲区指针
返回值:未解析的地址
*/
static char* print_number(cJSON* item, printbuffer* p)
{
	char* str = NULL;
	double d = item->valuedouble;
	if (0 == d)
	{
		if (NULL != p)						// 输出缓冲区结构体指针不为空  需要判断添加是否安全 
		{
			str = ensure(p, 2);				// 0转换成字符串 俩个字节  "0\\0"
		}
		else
		{
			str = (char*)cJSON_malloc(2);	// 为0申请空间
			if (NULL == str)				// 申请失败
			{
				return NULL;
			}

		}
		strcpy(str, "0");					// 拷贝0字符串
	}
	else if (fabs(((double)item->valueint) - d) <= DBL_EPSILON && d <= INT_MAX && d >= INT_MIN) // 判断是整型
	{
		if (NULL != p)
		{
			str = ensure(p, 11);			// 2 ^ 32 + 1(-4294967296,4294967297)可以用11个字符表示。
		}
		else
		{
			str = (char*)cJSON_malloc(11);	// 为2 ^ 32 + 1 申请空间
			if (NULL == str)				// 申请失败
			{
				return NULL;
			}
		}
		sprintf(str, "%d", item->valueint);	// 将整型值存储到str指向的缓冲区中
	}
	else	// double类型
	{
		if (p != NULL)						// 2 ^ 64 + 1(18446744073709551617)可以用21个字符表示。
		{
			str = ensure(p, 21);
		}
		else
		{
			str = (char*)cJSON_malloc(21);
			if (NULL == str)
			{
				return NULL;
			}
		}

		if (fabs(floor(d) - d) <= DBL_EPSILON && fabs(d) < 1.0e60)	// 无小数的double类型数字
		{
			sprintf(str, "%.0f", d);
		}
		else if (fabs(d) < 1.0e-6 || fabs(d) > 1.0e9)				// 范围在(1.0e-6,1.0e9)就用科学计数法
		{
			sprintf(str, "%e", d);
		}
		else														// 其他情况就用正常写法
		{
			sprintf(str, "%f", d);
		}	
	}
	return str;
}

parse_hex4

/* utf转换函数   */
static unsigned parse_hex4(const char* str)  
{
	unsigned h = 0;
	if (*str >= '0' && *str <= '9')
		h += (*str) - '0';
	else if (*str >= 'A' && *str <= 'F')
		h += 10 + (*str) - 'A';
	else if (*str >= 'a' && *str <= 'f')
		h += 10 + (*str) - 'a';
	else
		return 0;

	h = h << 4;
	str++;

	if (*str >= '0' && *str <= '9')
		h += (*str) - '0';
	else if (*str >= 'A' && *str <= 'F')
		h += 10 + (*str) - 'A';
	else if (*str >= 'a' && *str <= 'f')
		h += 10 + (*str) - 'a';
	else return 0;

	h = h << 4;
	str++;

	if (*str >= '0' && *str 以上是关于cJSON开源项目详细解剖的主要内容,如果未能解决你的问题,请参考以下文章

一文搞定json解析和封装问题,手把手带你学习CJSON开源代码

单片机使用 cJSON 开源库

单片机使用 cJSON 开源库

【Lua】cjson解析null

分享10个适合初学者学习的C开源项目代码

分享10个适合初学者学习的C开源项目代码