1、背景
cJSON用了很久,但是对它一直不太了解。这次向添加对long long类型的支持,一直出问题。因为有以前添加两位小数float的经历,我觉得会很轻松,没想到翻车了。于是有了这边文档,阅读了部分博主对cJSON的解析,给出自己的体悟。
1.1 参考文档
2 从使用者角度分析
2.1 数据结构上分析
cJSON在使用上来说有两种:
1、将json字符串输入得到key-value;
2、将key-value输入得到一个json字符串;
两者的桥梁就是cJSON提供结构体cJSON,由该结构体通过链表形成一个树来表征一个JSON。
一个cJSON结构体是对JSON数据的抽象。
/* The cJSON structure: */
typedef struct cJSON
{
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
int valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's number, if type==cJSON_Int64 */
long long valueint64;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
char *string;
} cJSON;
next、prev是链表的后继和前驱---兄弟节点;
child是子节点;
string 是该节点的key,而value 可以根据type类型来决定,是valuestring、valueint、valuedouble、valueint64。
接下去就是从使用角度分析,分别是组包JSON和解析JSON字符串两个角度出发。
2.2组包JSON
涉及到的函数如下所述,由创建cJSON、添加子节点、转成字符串、删除cJSON等过程。
/*创建节点*/
CJSON_PUBLIC(cJSON *)
cJSON_CreateArray(void);
CJSON_PUBLIC(cJSON *)
cJSON_CreateObject(void);
/*添加子节点*/
CJSON_PUBLIC(cJSON *)
cJSON_AddNumberToObject(cJSON *const object, const char *const name, const double number);
CJSON_PUBLIC(cJSON *)
cJSON_AddDoubleToObject(cJSON *const object, const char *const name, const double number);
CJSON_PUBLIC(cJSON *)
cJSON_AddInt64ToObject(cJSON *const object, const char *const name, const long long number);
CJSON_PUBLIC(cJSON *)
cJSON_AddStringToObject(cJSON *const object, const char *const name, const char *const string);
/*添加子节点2*/
CJSON_PUBLIC(void)
cJSON_AddItemToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(void)
cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
/*cJSON对象转字符串*/
/* Render a cJSON entity to text for transfer/storage. */
CJSON_PUBLIC(char *)
cJSON_Print(const cJSON *item);
CJSON_PUBLIC(void)
cJSON_Minify(char *json);
/*删除cJSON对象*/
/* Delete a cJSON entity and all subentities. */
CJSON_PUBLIC(void)
cJSON_Delete(cJSON *c);
每个过程看一两个函数实现
2.2.1 创建节点指定类型的节点
/* 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_PUBLIC(cJSON *)
cJSON_CreateObject(void)
{
cJSON *item = cJSON_New_Item(&global_hooks);
if (item)
{
item->type = cJSON_Object;
}
return item;
}
申请一个新的节点,成功则将类型设置成创建的类型,返回该节点。
这里涉及到一个类型的说法
/* cJSON Types: */
#define cJSON_Invalid (0) /*无实际意义值,初始化节点时状态*/
#define cJSON_False (1 << 0) /*布尔类型false*/
#define cJSON_True (1 << 1) /*布尔类型true*/
#define cJSON_NULL (1 << 2) /*空类型NULL*/
#define cJSON_Number (1 << 3) /*数值类型*/
#define cJSON_String (1 << 4) /*字符串类型*/
#define cJSON_Array (1 << 5) /*列表类型, child存储值*/
#define cJSON_Object (1 << 6) /*对象类型, child存储值*/
#define cJSON_Raw (1 << 7) /* raw json 表示valuestring中以\0结尾字符数据的任何类型*/
#define cJSON_Double (1 << 8) /*浮点类型*/
#define cJSON_Int64 (1 << 9) /*long long int类型*/
#define cJSON_Valid_Flags (0x03FF)
/*两个标志*/
#define cJSON_IsReference (512) /*标记child指向或valuestring不属于该节点,无需释放*/
#define cJSON_StringIsConst (1 << 10) /*string成员是一个常量,无需释放*/
如上述代码所述,用位来标记是什么类型的。
另外还有两个标志,分别
(1)表示该节点是个引用,其中的child和valuestring不属于该节点,释放时注意;
(2)表示该节点的string成员指向的是个常量,无需释放。
2.2.2 删除节点
/* Delete a cJSON structure. */
CJSON_PUBLIC(void)
cJSON_Delete(cJSON *item)
{
cJSON *next = NULL;
while (item != NULL) /*循环直至链表释放完*/
{
next = item->next; /*链表下一个兄弟节点*/
printf("item type 0x%x\n", item->type);
/*节点不带引用标志,且有子节点,则释放子节点*/
if (!(item->type & cJSON_IsReference) && (item->child != NULL))
{
cJSON_Delete(item->child);
}
/*节点不带引用标志,且有valuestring,则释放valuestring*/
if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL))
{
global_hooks.deallocate(item->valuestring);
}
/*节点不是常量标志,则释放string*/
if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))
{
global_hooks.deallocate(item->string);
}
/*释放节点本身*/
global_hooks.deallocate(item);
item = next; /*item=下一个兄弟节点*/
}
}
具体看代码注释
和创建节点中标志位一一对应。释放节点下子节点和一切动态分配的资源。
2.2.3添加子节点1
/*
* @fn add_item_to_object
* @param object The object to add to.
* @param string The string in key.
* @param item The item to add.
* @param hooks The hooks to use.
* @param constant_key Whether the key is a constant or not.
* @return true on success, false on failure.
*/
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)
{
if ((object == NULL) || (string == NULL) || (item == NULL))
{
return false;
}
if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))
{
hooks->deallocate(item->string);
}
if (constant_key)
{
item->string = (char *)cast_away_const(string);
item->type |= cJSON_StringIsConst;
}
else
{
char *key = (char *)cJSON_strdup((const unsigned char *)string, hooks);
if (key == NULL)
{
return false;
}
item->string = key;
item->type &= ~cJSON_StringIsConst;
}
return add_item_to_array(object, item);
}
static cJSON_bool add_item_to_array(cJSON *array, cJSON *item)
{
cJSON *child = NULL;
if ((item == NULL) || (array == NULL))
{
return false;
}
child = array->child;
if (child == NULL)
{
/* list is empty, start new one */
array->child = item;
}
else
{
/* append to the end */
while (child->next)
{
child = child->next;
}
suffix_object(child, item);
}
return true;
}
/*
* @fn cJSON_AddStringToObject
* @param object The object to add to.
* @param name The name of the item to add.
* @param string The string to add.
* @return The new item, or NULL on failure.
*/
CJSON_PUBLIC(cJSON *)
cJSON_AddStringToObject(cJSON *const object, const char *const name, const char *const string)
{
cJSON *string_item = cJSON_CreateString(string);
if (add_item_to_object(object, name, string_item, &global_hooks, false))
{
return string_item;
}
cJSON_Delete(string_item);
return NULL;
}
cJSON_AddStringToObject函数向object节点中添加key是name, value是string的子节点,先将value复制给了valuestring成员;
在add_item_to_object根据constant_key的值,给key赋了值,对type成员设置关于cJSON_StringIsConst标志的值;
add_item_to_array将子节点添加在父节点的child成员指向的链表下
2.2.4 添加子节点2
CJSON_PUBLIC(void)
cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item)
{
add_item_to_object(object, string, item, &global_hooks, false);
}
也调用了add_item_to_object,不再叙述
2.2.5 cJSON结构体转字符串
static unsigned char *print(const cJSON *const item, cJSON_bool format, const internal_hooks *const hooks)
{
static const size_t default_buffer_size = 256;
printbuffer buffer[1];
unsigned char *printed = NULL;
memset(buffer, 0, sizeof(buffer));
/* create buffer */
buffer->buffer = (unsigned char *)hooks->allocate(default_buffer_size);
buffer->length = default_buffer_size;
buffer->format = format;
buffer->hooks = *hooks;
if (buffer->buffer == NULL)
{
goto fail;
}
/* print the value */
if (!print_value(item, buffer))
{
goto fail;
}
update_offset(buffer);
/* check if reallocate is available */
if (hooks->reallocate != NULL)
{
printed = (unsigned char *)hooks->reallocate(buffer->buffer, buffer->offset + 1);
buffer->buffer = NULL;
if (printed == NULL)
{
goto fail;
}
}
else /* otherwise copy the JSON over to a new buffer */
{
printed = (unsigned char *)hooks->allocate(buffer->offset + 1);
if (printed == NULL)
{
goto fail;
}
memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1));
printed[buffer->offset] = '\0'; /* just to be sure */
/* free the buffer */
hooks->deallocate(buffer->buffer);
}
return printed;
fail:
if (buffer->buffer != NULL)
{
hooks->deallocate(buffer->buffer);
}
if (printed != NULL)
{
hooks->deallocate(printed);
}
return NULL;
}
/* Render a cJSON item/entity/structure to text. */
CJSON_PUBLIC(char *)
cJSON_Print(const cJSON *item)
{
return (char *)print(item, true, &global_hooks);
}
简单就是cJSON_Print
->print_value
->realloc
->return printed;
核心看print_value
/* 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;
}
/*排除标志影响,直接看item是什么类型的*/
switch ((item->type) & cJSON_Valid_Flags)
{
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_Double:
return print_double(item, output_buffer);
case cJSON_Int64:
return print_int64(item, output_buffer);
case cJSON_Raw:
{
size_t raw_length = 0;
if (item->valuestring == NULL)
{
if (!output_buffer->noalloc)
{
output_buffer->hooks.deallocate(output_buffer->buffer);
}
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;
}
}
根据cJSON对象所述类型进行字符串化,我们关注一个object、array、num这三种类型,string类型用脚趾都能想到。
2.2.5.1 print_object
/* Render an object to text. */
static cJSON_bool print_object(const cJSON *const item, printbuffer *const output_buffer)
{
unsigned char *output_pointer = NULL;
size_t length = 0;
cJSON *current_item = item->child;
if (output_buffer == NULL)
{
return false;
}
/* Compose the output: */
length = (size_t)(output_buffer->format ? 2 : 1); /* fmt: {\n */
output_pointer = ensure(output_buffer, length + 1);
if (output_pointer == NULL)
{
return false;
}
/*花括号前部分*/
*output_pointer++ = '{';
output_buffer->depth++;
if (output_buffer->format)
{
*output_pointer++ = '\n';
}
output_buffer->offset += length;
while (current_item)
{
if (output_buffer->format)
{
size_t i;
output_pointer = ensure(output_buffer, output_buffer->depth);
if (output_pointer == NULL)
{
return false;
}
for (i = 0; i < output_buffer->depth; i++)
{
*output_pointer++ = '\t';
}
output_buffer->offset += output_buffer->depth;
}
/*子节点的key*/
/* print key */
if (!print_string_ptr((unsigned char *)current_item->string, output_buffer))
{
return false;
}
update_offset(output_buffer);
length = (size_t)(output_buffer->format ? 2 : 1);
output_pointer = ensure(output_buffer, length);
if (output_pointer == NULL)
{
return false;
}
/*key-value分隔符*/
*output_pointer++ = ':';
if (output_buffer->format)
{
*output_pointer++ = '\t';
}
output_buffer->offset += length;
/*子节点value,调用print_value*/
/* print value */
if (!print_value(current_item, output_buffer))
{
return false;
}
update_offset(output_buffer);
/* print comma if not last */
length = (size_t)((output_buffer->format ? 1 : 0) + (current_item->next ? 1 : 0));
output_pointer = ensure(output_buffer, length + 1);
if (output_pointer == NULL)
{
return false;
}
if (current_item->next)
{
/*如果子节点有下一个兄弟节点,加逗号*/
/* print value */
*output_pointer++ = ',';
}
if (output_buffer->format)
{
*output_pointer++ = '\n';
}
*output_pointer = '\0';
output_buffer->offset += length;
current_item = current_item->next;
}
output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2);
if (output_pointer == NULL)
{
return false;
}
if (output_buffer->format)
{
size_t i;
for (i = 0; i < (output_buffer->depth - 1); i++)
{
*output_pointer++ = '\t';
}
}
/*花括号后部分*/
*output_pointer++ = '}';
*output_pointer = '\0';
output_buffer->depth--;
return true;
}
object类型的cJSON对象输出字符串类似下图,具体格式细节还是根据format来控制
{
"child_key":"child_value"
}
format为true时,配合\t和深度来缩进完成格式化。
2.2.5.2 print_array
/* Render an array to text */
static cJSON_bool print_array(const cJSON *const item, printbuffer *const output_buffer)
{
unsigned char *output_pointer = NULL;
size_t length = 0;
cJSON *current_element = item->child;
if (output_buffer == NULL)
{
return false;
}
/* Compose the output array. */
/* opening square bracket */
output_pointer = ensure(output_buffer, 1);
if (output_pointer == NULL)
{
return false;
}
/*列表中括号前部分*/
*output_pointer = '[';
output_buffer->offset++;
output_buffer->depth++;
while (current_element != NULL)
{ /*子节点的value*/
if (!print_value(current_element, output_buffer))
{
return false;
}
update_offset(output_buffer);
if (current_element->next)
{
length = (size_t)(output_buffer->format ? 2 : 1);
output_pointer = ensure(output_buffer, length + 1);
if (output_pointer == NULL)
{
return false;
}
/*有下一个对象,则添加列表对象间分隔符*/
*output_pointer++ = ',';
if (output_buffer->format)
{
*output_pointer++ = ' ';
}
*output_pointer = '\0';
output_buffer->offset += length;
}
current_element = current_element->next;
}
output_pointer = ensure(output_buffer, 2);
if (output_pointer == NULL)
{
return false;
}
/*中括号后部分*/
*output_pointer++ = ']';
*output_pointer = '\0';
output_buffer->depth--;
return true;
}
输出为
[子节点值,子节点值]
这都是从结果来推到需求,写代码时从需求到结果,其实更好的分析方法是想自己该如何实现它。
2.2.5.3 print_number
/* Render the number nicely from the given item into a string. */
static cJSON_bool print_number(const cJSON *const item, printbuffer *const output_buffer)
{
unsigned char *output_pointer = NULL;
double d = item->valuedouble;
int length = 0;
size_t i = 0;
unsigned char number_buffer[26]; /* temporary buffer to print the number into */
unsigned char decimal_point = get_decimal_point();
double test;
if (output_buffer == NULL)
{
return false;
}
/* This checks for NaN and Infinity */
if ((d * 0) != 0)
{
length = sprintf((char *)number_buffer, "null");
}
else
{
/* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */
length = sprintf((char *)number_buffer, "%1.15g", d);
/* Check whether the original double can be recovered */
if ((sscanf((char *)number_buffer, "%lg", &test) != 1) || ((double)test != d))
{
/* If not, print with 17 decimal places of precision */
length = sprintf((char *)number_buffer, "%1.17g", d);
}
}
/* sprintf failed or buffer overrun occured */
if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1)))
{
return false;
}
/* reserve appropriate space in the output */
output_pointer = ensure(output_buffer, (size_t)length + sizeof(""));
if (output_pointer == NULL)
{
return false;
}
/* copy the printed number to the output and replace locale
* dependent decimal point with '.' */
for (i = 0; i < ((size_t)length); i++)
{
if (number_buffer[i] == decimal_point)
{
output_pointer[i] = '.';
continue;
}
output_pointer[i] = number_buffer[i];
}
output_pointer[i] = '\0';
output_buffer->offset += (size_t)i;
return true;
}
%1.15g
%
:格式化输出的开始符号1.15
:表示输出的总宽度为1,小数点后保留15位有效数字g
:以指数形式输出浮点数
按上述获取长度后,从double类型的valuedouble中转化为字符串
2.3 解析JSON
涉及到函数有
/*解析*/
CJSON_PUBLIC(cJSON *)
cJSON_Parse(const char *value);
/*获取节点数目或节点*/
CJSON_PUBLIC(int)
cJSON_GetArraySize(const cJSON *array);
/* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */
CJSON_PUBLIC(cJSON *)
cJSON_GetArrayItem(const cJSON *array, int index);
/* Get item "string" from object. Case insensitive. */
CJSON_PUBLIC(cJSON *)
cJSON_GetObjectItem(const cJSON *const object, const char *const string);
2.3.1 解析
/* Parse an object - create a new root, and populate. */
CJSON_PUBLIC(cJSON *)
cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated)
{
parse_buffer buffer = {0, 0, 0, 0, {0, 0, 0}};
cJSON *item = NULL;
/* reset error position */
global_error.json = NULL;
global_error.position = 0;
if (value == NULL)
{
goto fail;
}
buffer.content = (const unsigned char *)value;
buffer.length = strlen((const char *)value) + sizeof("");
buffer.offset = 0;
buffer.hooks = global_hooks;
/*创建根节点*/
item = cJSON_New_Item(&global_hooks);
if (item == NULL) /* memory fail */
{
goto fail;
}
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 */
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;
}
CJSON_PUBLIC(cJSON *)
cJSON_Parse(const char *value)
{
return cJSON_ParseWithOpts(value, 0, 0);
}
核心还是来到了parse_value
/* 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 */
/* null */
if (can_read(input_buffer, 4) && (strncmp((const char *)buffer_at_offset(input_buffer), "null", 4) == 0))
{
item->type = cJSON_NULL;
input_buffer->offset += 4;
return true;
}
/* false */
if (can_read(input_buffer, 5) && (strncmp((const char *)buffer_at_offset(input_buffer), "false", 5) == 0))
{
item->type = cJSON_False;
input_buffer->offset += 5;
return true;
}
/* true */
if (can_read(input_buffer, 4) && (strncmp((const char *)buffer_at_offset(input_buffer), "true", 4) == 0))
{
item->type = cJSON_True;
item->valueint = 1;
input_buffer->offset += 4;
return true;
}
/* string */
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"'))
{
return parse_string(item, input_buffer);
}
/* 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);
}
/* array */
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '['))
{
return parse_array(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;
}
针对各自类型的特点开始解析,根据这里也没有啥特殊的,最终都化为字符串和数值。
2.3.2 获取节点数目
获取子节点的个数,子节点链表中对象数目。
2.3.3 获取节点
只能获取子节点的信息,采取一级一级剥洋葱的方式。
3、添加对long long类型的支持
有博主是在cJSON_NUM类型下开了个子类型,逻辑也是相当清晰,本文在大的类型里添加。
没有啥难度,照葫芦画瓢,把类型和标志的意义搞懂就没出过BUG了。