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.c
和cJSON.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。
- Number类型,则valueint或valuedouble中存储着值。
- int类型,则valueint中存储着值
- double类型,则valuedouble中存储着值。
- 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开源项目详细解剖的主要内容,如果未能解决你的问题,请参考以下文章