cJSON.c
给各种接口分好类后,开始细看它们的实现。
除了cJSON.h以外用了一下头文件
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <limits.h>
#include <ctype.h>
#include <float.h>
一、内存管理相关
typedef struct internal_hooks
{
void *(CJSON_CDECL *allocate)(size_t size);
void (CJSON_CDECL *deallocate)(void *pointer);
void *(CJSON_CDECL *reallocate)(void *pointer, size_t size);
} internal_hooks;
...
...
static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc };
定义了internal_hooks结构体,并创建了一个global_hooks用来管理所有的cJSON对象的内存分配allocate、回收deallocate和重新分配reallocate。三种内存管理的实现的方式是调用<stdlib.h>里的malloc、free、realloc。作者给你封装一下方便我们自定义自己的内存管理,比如说你要是想在cJSON对象销毁前,暂存这个对象的数据30天,便可在free前进行操作。
static void CJSON_CDECL internal_free(void *pointer)
{
free(pointer);
}
二、创建Creat相关
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean)
{
cJSON *item = cJSON_New_Item(&global_hooks);
if(item)
{
item->type = boolean ? cJSON_True : cJSON_False;
}
return item;
}
cJSON_CreateNull、cJSON_CreateTrue、cJSON_CreateFalse、cJSON_CreateBool、cJSON_CreateArray、cJSON_CreateObject均与以上代码类似,其中调用的cJSON_New_Item是使用自定义的hooks结构体里的allocate方法申请内存,在申请成功后,将内存全部置’\0’
/* Internal constructor. */
static cJSON *cJSON_New_Item(const internal_hooks * const hooks)
{
cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON));
if (node)
{
memset(node, '\0', sizeof(cJSON));
}
return node;
}
cJSON_CreateNumber和cJSON_CreateString、cJSON_CreateRaw除了对该cJSON对象的type赋值外,还需额外赋值valuedouble、valuestring。(很好理解吧,你创建一个数字或者字符串类型的cJSON对象,总要把该对象的值给记录下来吧)
除此之外还有一些实现的细节:
- 对数字的创建,判断了数字是否在 INT_MIN 到 INT_MAX 之间 超出范围的直接设为两个边界值。
- 对字符串的创建,调用了cJSON_strdup进行内存的拷贝。如果拷贝失败(返回NULL)则字符串cJSON也创建失败,销毁该item,返回NULL。
static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks)
{
size_t length = 0;
unsigned char *copy = NULL;
if (string == NULL)
{
return NULL;
}
length = strlen((const char*)string) + sizeof("");
copy = (unsigned char*)hooks->allocate(length); // 用hooks去申请一块地址
if (copy == NULL)
{
return NULL;
}
memcpy(copy, string, length); // 拷贝
return copy;
}
cJSON_CreateArrayReference 、 cJSON_CreateObjectReference 、 cJSON_CreateStringReference 均类似多了一步去除const关键字修饰(因为需要动态增长)
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) {
cJSON *item = cJSON_New_Item(&global_hooks);
if (item != NULL) {
item->type = cJSON_Array | cJSON_IsReference;
item->child = (cJSON*)cast_away_const(child); // cast_away_const
}
return item;
}
/* helper function to cast away const */
static void* cast_away_const(const void* string)
{
return (void*)string;
}
拥有以上接口,可以实现创建各种头结点,而后调用添加接口,举例如下
cjson = cJSON_CreateArray();
cJSON_AddItemToArray(cjson, cJSON_CreateString( "C" ));
cJSON_AddItemToArray(cjson, cJSON_CreateString( "Java" ));
除了一个个手动添加数组元素外,还提供了用数组直接创建cJSON对象的四个接口。cJSON_CreateIntArray、cJSON_CreateFloatArray、cJSON_CreateDoubleArray、cJSON_CreateStringArray。其传入的参数均为两个,数组首地址和数组元素个数。以cJSON_CreateIntArray为例:
/* Create Arrays: */
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count)
{
size_t i = 0; // 用来迭代取数的
cJSON *n = NULL; // 新创建的Number类型的cJSON
cJSON *p = NULL; // 上一个Number;因为是双向链表实现的,所以需要记录上一个结点
cJSON *a = NULL; // Array类型的cJSON
if ((count < 0) || (numbers == NULL))
{ // 如果数组元素个数小于0,或者数组不存在
return NULL;
}
// 创建数组cJSON
a = cJSON_CreateArray();
// 它真的好严谨啊,每次都会检查创建操作是否成功
for(i = 0; a && (i < (size_t)count); i++)
{
n = cJSON_CreateNumber(numbers[i]); // DoubleArray、StringArray都类似
if (!n)
{ // 创建不成功啊
cJSON_Delete(a);
return NULL;
}
if(!i)
{ // i=0时,即将array父结点child指向第一个number结点
// 这里没有将首个number节点的prev指向array父结点
a->child = n;
}
else
{// 数组双向链表,链表内部用prev和next连接
suffix_object(p, n);
}
p = n;
}
// 这里将首个number结点的prev指向最后一个number节点,组成循环链表
// 便于插入时直接找到链表末尾
if (a && a->child) {
a->child->prev = n;
}
return a;
}
/* Utility for array list handling. */
static void suffix_object(cJSON *prev, cJSON *item)
{
prev->next = item;
item->prev = prev;
}
(假如我用c++来写能不能使用函数的重载、模板等方法来简化以上代码?毕竟它四种接口写了四遍啊,明明里面只有两句不同。)
三、添加Add相关
Add最核心两个函数 add_item_to_array 和 add_item_to_object
static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) // 静态函数
{
cJSON *child = NULL;
if ((item == NULL) || (array == NULL) || (array == item))
{
return false;
}
child = array->child;
/*
* To find the last item in array quickly, we use prev in array
在cJSON_CreateIntArray里有看到过,
父节点array里的首个child的prev指向的是最后一个元素
*/
if (child == NULL)
{
/* list is empty, start new one */
array->child = item;
item->prev = item;
item->next = NULL;
}
else
{
/* append to the end */
if (child->prev)
{
suffix_object(child->prev, item); // 这个前面介绍过了
array->child->prev = item;
}
}
return true;
}
/*
函数解释:在object里面添加一个名为string的item,使用hooks来进行内存分配,constant_key来判断对于名字string的选择直接使用,还是拷贝副本使用。
*/
static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key)
{
char *new_key = NULL;
int new_type = cJSON_Invalid;
if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item))
{
return false;
}
if (constant_key)
{// 将传入的string 去掉它的const修饰直接作为名字标识
new_key = (char*)cast_away_const(string);
new_type = item->type | cJSON_StringIsConst;
}
else
{// 拷个string副本
new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks);
if (new_key == NULL)
{
return false;
}
new_type = item->type & ~cJSON_StringIsConst;
}
if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))
{
hooks->deallocate(item->string);
}
item->string = new_key;
item->type = new_type;
// 所以说更本质的还是add_item_to_array
return add_item_to_array(object, item);
}
cJSON_AddItemToArray、cJSON_AddItemToObject、cJSON_AddItemToObjectCS,均是包装了一下,在对参数进行一些处理后都使用的是静态函数add_item_to_array进行添加。不同的是ToObeject和ToObjectCS,从逻辑上是将一个cJSON嵌入进另一个cJSON,所以多了一个参数string需要对待嵌入的cJSON对象命名,而涉及字符串的操作,便实现了两个版本:ToObeject是将string拷贝一份副本作为名字,ToObjectCS是直接将该字符串作为名字。
/* Add item to array/object. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item)
{
return add_item_to_array(array, item); // 包装了一下
}
其他的基本同理,均是对需要添加的Item进行创建或拷贝后将新的cJSON Add进去。
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name)
{
cJSON *array = cJSON_CreateArray();
if (add_item_to_object(object, name, array, &global_hooks, false))
{
return array;
}
cJSON_Delete(array);
return NULL;
}
四、分离Detach删除Delete更新Update
分离Detach也是在套娃,Delete更是直接先通过Detach分出来然后递归释放掉内存
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which)
{ // 对用户友好一点,只要给出 array 和 待删数据的下标
if (which < 0)
{
return NULL;
}
// 都是调cJSON_DetachItemViaPointer分离
return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which));
}
cJSON_DetachItemViaPointer是从parent指向的链表数组中找item并且扒拉下来。很好理解不赘述了。
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item)
{
if ((parent == NULL) || (item == NULL))
{
return NULL;
}
if (item != parent->child)
{
/* not the first element */
item->prev->next = item->next;
}
if (item->next != NULL)
{
/* not the last element */
item->next->prev = item->prev;
}
if (item == parent->child)
{
/* first element */
parent->child = item->next;
}
else if (item->next == NULL)
{
/* last element */
parent->child->prev = item->prev;
}
/* make sure the detached item doesn't point anywhere anymore */
item->prev = NULL;
item->next = NULL;
return item;
}
删除都是这个cJSON_Delete。cJSON如果嵌套了cJSON要递归删,cJSON如果是指针或者字符串类型需要释放对应的string
/* Delete a cJSON structure. */
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item)
{
cJSON *next = NULL;
while (item != NULL)
{
next = item->next; // 记录下一个要删的
if (!(item->type & cJSON_IsReference) && (item->child != NULL))
{ // 如果该item是个嵌套的cJSON,要递归删child里面的
cJSON_Delete(item->child);
}
if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL))
{
global_hooks.deallocate(item->valuestring);
}
if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))
{
global_hooks.deallocate(item->string);
}
global_hooks.deallocate(item);
item = next;
}
}
Update实现了:
- 在数组指定位置插入一个item——cJSON_InsertItemInArray
- 在数组指定位置替换——cJSON_ReplaceItemInArray而后调用cJSON_ReplaceItemViaPointer
- 在cJSON指定name里替换——cJSON_ReplaceItemInObject和其CS版本(同上面的Add 的CS版本作用一样)最终还是调到cJSON_ReplaceItemViaPointer这个函数
// 代码贴过来我累了不想看了
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement)
{
if ((parent == NULL) || (parent->child == NULL) || (replacement == NULL) || (item == NULL))
{
return false;
}
if (replacement == item)
{
return true;
}
replacement->next = item->next;
replacement->prev = item->prev;
if (replacement->next != NULL)
{
replacement->next->prev = replacement;
}
if (parent->child == item)
{
if (parent->child->prev == parent->child)
{
replacement->prev = replacement;
}
parent->child = replacement;
}
else
{ /*
* To find the last item in array quickly, we use prev in array.
* We can't modify the last item's next pointer where this item was the parent's child
*/
if (replacement->prev != NULL)
{
replacement->prev->next = replacement;
}
if (replacement->next == NULL)
{
parent->child->prev = replacement;
}
}
item->next = NULL;
item->prev = NULL;
cJSON_Delete(item);
return true;
}
五、解析Parse
解析一共有四个相关的,分别为
-
cJSON_ParseWithLengthOpts:最全面的,允许设置待解析的字符数组地址,长度,是否返回数组解析末尾,是否要求末尾不加’\0’;
-
cJSON_ParseWithOpts:相当于默认给你设置好了length,然后调用了上面这个WithLengthOpts
-
cJSON_ParseWithLength:相当于默认设好了Opts
-
cJSON_Parse:最偷懒的,你只要给一个字符数组给它,另外都默认给你掉
后面三个最终都是会调用到第一个,来看第一个接口
/* Parse an object - create a new root, and populate. */
/* 参数解释:并不是直接在value上进行解析,而是拷贝一份并添加长度等信息得到parse_buffer进行解析
value:形如“{"color":{"blue", "white"}}” 这样的char*数组,末尾可能有'\0',中间可能有空格
buffer_length:待解析的长度
return_parse_end:返回解析后buffer的位置
require_null_terminated:是否对buffer进行删空格等处理
*/
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated)
{
// 专门定义了一个数据结构来存放待解析的字符串
parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } };
/*parse_buffer长这样
typedef struct
{
const unsigned char *content;
size_t length;
size_t offset;
size_t depth; // 输入在数组或者对象的嵌套深度
internal_hooks hooks;
} parse_buffer;
*/
cJSON *item = NULL;
/* reset error position */
global_error.json = NULL;
global_error.position = 0;
if (value == NULL || 0 == buffer_length)
{
goto fail;
}
// 赋值
buffer.content = (const unsigned char*)value;
buffer.length = buffer_length;
buffer.offset = 0;
buffer.hooks = global_hooks;
item = cJSON_New_Item(&global_hooks);
if (item == NULL) /* memory fail */
{
goto fail;
}
// parse_value这里开始解析,详细见下个代码框
if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer))))
{
/* parse failure. ep is set. */
goto fail;
}
/* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */
// 可以这样子调用cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0);
if (require_null_terminated)
{// 对应第三个参数
buffer_skip_whitespace(&buffer);
if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0')
{
goto fail;
}
}
if (return_parse_end)
{// 对应第四个参数
*return_parse_end = (const char*)buffer_at_offset(&buffer);
}
return item;
fail:// 越看越觉得好代码的细节真是考虑得周到
if (item != NULL)
{
cJSON_Delete(item);
}
if (value != NULL)
{
error local_error;
local_error.json = (const unsigned char*)value;
local_error.position = 0;
if (buffer.offset < buffer.length)
{
local_error.position = buffer.offset;
}
else if (buffer.length > 0)
{
local_error.position = buffer.length - 1;
}
if (return_parse_end != NULL)
{
*return_parse_end = (const char*)local_error.json + local_error.position;
}
global_error = local_error;
}
return NULL;
}
parse_value的代码,根据value的type不同进行解析,’123‘得解析为数字吧,’abc‘得解析为sting吧。挑两个出来看看:type为number和obeject的。
/* Parser core - when encountering text, process appropriately. */
static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer)
{
if ((input_buffer == NULL) || (input_buffer->content == NULL))
{
return false; /* no input */
}
/* parse the different types of values */
...
...
/* number */
if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9'))))
{
return parse_number(item, input_buffer);
}
...
...
/* object */
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{'))
{
return parse_object(item, input_buffer);
}
return false;
}
都用到了can_access_at_index和buffer_at_offset。这两个是宏函数,预处理器用复制宏代码的方式代替函数调用,省去了参数压栈、生成汇编语言的CALL调用、返回参数、执行return等过程。简单来说就是提升效率。作用从名字中可以看出来。
#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length))
#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset)
还调用了各自的parse_number和parse_object。
/* Parse the input text to generate a number, and populate the result into item. */
static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer)
{
double number = 0;
unsigned char *after_end = NULL;
unsigned char number_c_string[64];
unsigned char decimal_point = get_decimal_point();// 它小数点都考虑到了不同地区的小数点还不同,在<locale.h>中,感兴趣看
// https://www.runoob.com/cprogramming/c-standard-library-locale-h.html
size_t i = 0;
if ((input_buffer == NULL) || (input_buffer->content == NULL))
{
return false;
}
/* copy the number into a temporary buffer and replace '.' with the decimal point
* of the current locale (for strtod)
* This also takes care of '\0' not necessarily being available for marking the end of the input */
for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++)
{// 这里在把“123.346”拆开成一个一个的放入number_c_string中
switch (buffer_at_offset(input_buffer)[i])
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '+':
case '-':
case 'e':// 牛逼还考虑指数
case 'E':
number_c_string[i] = buffer_at_offset(input_buffer)[i];
break;
case '.':
number_c_string[i] = decimal_point;
break;
default:
goto loop_end;
}
}
loop_end:
number_c_string[i] = '\0'; // c风格的字符串补个\0
// strtod是<stdlib.h>库里面的
number = strtod((const char*)number_c_string, (char**)&after_end);
if (number_c_string == after_end)
{
return false; /* parse_error */
}
item->valuedouble = number;
/* 溢出的处理 */
if (number >= INT_MAX)
{
item->valueint = INT_MAX;
}
else if (number <= (double)INT_MIN)
{
item->valueint = INT_MIN;
}
else
{
item->valueint = (int)number;
}
item->type = cJSON_Number;
// 更新offset
input_buffer->offset += (size_t)(after_end - number_c_string);
return true;
}
parse_object实在是长的有点可怕了,全贴过来显得会很臃肿,故删掉了一些判断内存是否分配成功这些异常处理的代码。
static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer)
{
cJSON *head = NULL; /* linked list head */
cJSON *current_item = NULL;
input_buffer->depth++; // depth是标识嵌套深度的
input_buffer->offset++; // ++是因为首个字符一定是{
// 跳过空格,相当于找到第一个有效字符
buffer_skip_whitespace(input_buffer);
/* step back to character in front of the first element */
input_buffer->offset--;
/* loop through the comma separated array elements */
do
{
/* allocate next item */
cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks));
/* attach next item to list */
if (head == NULL)
{
/* start the linked list */
current_item = head = new_item;
}
else
{
/* add to the end and advance */
current_item->next = new_item;
new_item->prev = current_item;
current_item = new_item;
}
/* parse the name of the child 一定是string */
input_buffer->offset++;
buffer_skip_whitespace(input_buffer);
// 解析出来子对象的名字
if (!parse_string(current_item, input_buffer))
{
goto fail; /* failed to parse name */
}
buffer_skip_whitespace(input_buffer);
// 因为调用的parse_string本来是用来解析string类型
// 专门赋值作为valuestring而不是作为名字的
/* swap valuestring and string, because we parsed the name */
current_item->string = current_item->valuestring;
current_item->valuestring = NULL;
/* parse the value */
input_buffer->offset++;
buffer_skip_whitespace(input_buffer);
// 你看这就是开始递归调用前面介绍过的parse_value
if (!parse_value(current_item, input_buffer))
{
goto fail; /* failed to parse value */
}
buffer_skip_whitespace(input_buffer);
}
while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); // do-while 循环
success: // fail 的被我删了,可以自己去看
input_buffer->depth--; // 嵌套层数--,虽然目前我还没看出来记录个嵌套层数有什么用
if (head != NULL) {
head->prev = current_item;
}
item->type = cJSON_Object;
item->child = head;
input_buffer->offset++;
return true;
}
六、获取Get输出Print
获取Get不就是简单版的删除Delete嘛,找出来,不用删罢了。
输出Print的各自接口,主要是静态函数print,它定义了printbuffer等等(前面是pares_buffer,这个又不加下划线了)然后交给静态函数print_value去根据type将value放到printbuffer里面。
static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks)
/* Render a value to text. */
static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer)
{
unsigned char *output = NULL;
if ((item == NULL) || (output_buffer == NULL))
{
return false;
}
switch ((item->type) & 0xFF) // 没懂这么&一下是干嘛
{
case cJSON_NULL:
output = ensure(output_buffer, 5);
if (output == NULL)
{
return false;
}
strcpy((char*)output, "null");
return true;
case cJSON_False:
output = ensure(output_buffer, 6);
if (output == NULL)
{
return false;
}
strcpy((char*)output, "false");
return true;
case cJSON_True:
output = ensure(output_buffer, 5);
if (output == NULL)
{
return false;
}
strcpy((char*)output, "true");
return true;
case cJSON_Number:
return print_number(item, output_buffer);
case cJSON_Raw:
{
size_t raw_length = 0;
if (item->valuestring == NULL)
{
return false;
}
raw_length = strlen(item->valuestring) + sizeof("");
output = ensure(output_buffer, raw_length);
if (output == NULL)
{
return false;
}
memcpy(output, item->valuestring, raw_length);
return true;
}
case cJSON_String:
return print_string(item, output_buffer);
case cJSON_Array:
return print_array(item, output_buffer);
case cJSON_Object: // 脑子痛起来了
return print_object(item, output_buffer);
default:
return false;
}
}
总结
其实看到Get和Print接口时开始看不进去了,因为发现都大同小异——在双向循环可嵌套的链表上增删改查。在把数据结构设计好后(cJSON,buffer,hooks,error等)剩下的就是细心和耐心考虑多种来自用户的不规范输入、系统时不时内存分配不成功(string,及各类指针)、数据溢出、编码格式等等细节,出现异常后撤销分配的内存等已经执行的操作。在代码结构上,对用户仅提供了可以调用的接口,真正对cJSON链表进行操作的代码是作为静态函数隐藏起来的。
看完这些代码最直观的一个感受是 它的异常处理部分特别的完善。3k行可能2k都在处理异常。respect