引言(Introduction)
写在文章开头的一句话,不积硅步,无以致千里;不积小流,无以成江海。
第二部分应用篇对于普通的应用场景已经足够了,但是还是缺少了字符串解析的功能,并且字符串解析也是cJSON中非常重要的模块。解析字符串只有几个函数,并且函数内容也比较简单,所以本文更注重其源码的实现。
1. 实例(Example)
在分析源码之前,先使用cJSON提供的接口来解析字符串,下面是一个实例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cJSON.h"
// {
// "name": "Zhang San", // string
// "sex": 1, // boolen
// "height": 1.8
// "family": [
// {
// "name": "Zhang Si",
// "relationship": "Father"
// },
// {
// "name": "Li Si",
// "relationship": "Mother"
// }
// ],
// "birthday": {
// "year": 2000,
// "month": 1,
// "day":1
// },
// }
int CJSON_CDECL main(void){
const char *json = "{\"name\":\"Zhang San\", \"sex\":true, \"height\":1.8,\
\"family\":[{\"name\":\"Zhang Si\",\"relationship\":\"Father\"},\
{\"name\":\"Li Si\",\"relationship\":\"Mother\"}],\
\"brithday\":{\"year\":2000, \"month\":1, \"day\":1}}";
char *output = NULL;
cJSON *root = NULL;
root = cJSON_Parse(json);
output = cJSON_Print(root);
printf("%s\n", output);
free(output);
cJSON_Delete(root);
return 0;
}
上面例子中只使用到了接口"cJSON_Parse",其将json指向的字符串解析到cJSON指针root指向的结构体中。被解析的字符串需要使用一定的格式,这边根据源码直接给出注意事项:
- 解析的对象是字符串,即C/C++中的字符串;
- 一个json结构需要以 ’ { ’ 为开头,以 ’ } ’ 结束;
- 一个json数组需要以 ’ [ ’ 为开头,以 ’ ] ’ 结束;
- 键值对之间用 ’ : ’ 表示附属关系;
- 字符串的值需要以 \" 为开头,同时以 \" 结束;
- 取消转义字符需要在转义字符前面添加 \\ ;
- 数字型的值不需要使用双引号等特别注明,只需要写明数字即可。
2. 源码(Source code)
cJSON提供给用户的接口有"cJSON_Parse",“cJSON_ParseWithLength”,“cJSON_ParseWithOpts”,“cJSON_ParseWithLengthOpts”。不管调用哪个接口,最终都会调用到"cJSON_ParseWithLengthOpts"函数。
下面直接给出前三者的函数声明以及函数定义部分:
-
“cJSON_Parse()”
// cJSON.h // 该函数为默认参数版本,即不返回解析末端以及不检查'\0'是否是作为结尾。 CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); // cJSON.c CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) { return cJSON_ParseWithOpts(value, 0, 0); }
-
“cJSON_ParseWithOpts()”
// cJSON.h // 该函数为选项解析版本,可以返回解析末端的字符,以及是否检查解析结果的结尾是否为'\0' CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); // cJSON.c CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) { // buffer_length用于存储解析结果的长度 size_t buffer_length; if (NULL == value) { return NULL; } // 在结尾添加空字符的大小 buffer_length = strlen(value) + sizeof(""); return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); }
-
“cJSON_ParseWithLength()”
// cJSON.h // 该函数为按照给定的长度对字符串进行解析 CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length); // cJSON.c CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length) { return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); }
在正式介绍"cJSON_ParseWithLengthOpts"的源码之前,需要先介绍使用到的结构体:
-
“parse_buffer”
// 用于存放解析过程中的信息的结构体 typedef struct { const unsigned char *content; // 被解析的字符串 size_t length; // 被解析的字符串长度 size_t offset; // 记录当前解析的位置 size_t depth; // 记录当前解析结果的深度 internal_hooks hooks; // 用于分配内存与释放内存的钩子 } parse_buffer;
-
“internal_hooks”
// 用于分配内存与释放内存的钩子 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 }; // "cJSON.c"文件使用的全局变量,用于对cJSON实例分配内存
-
“error”
// 包含错误信息的结构体 typedef struct { const unsigned char *json; // 指向错误信息的字符串指针 size_t position; // 错误信息的位置 } error; static error global_error = { NULL, 0 }; // "cJSON.c"文件使用的全局变量,用于存储各个函数发生错误的信息
最后终于可以介绍本文的重头戏了,"cJSON_ParseWithLengthOpts"的源码如下:
// cJSON.h
// 该函数为提供了所有选项的字符串解析版本,包括解析长度,返回解析末端信息,使用'\0'作为结尾
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
// cJSON.c
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated)
{
// param: value, the string to be parsed;
// param: buffer_length, the length of the string to be parsed;
// param: return_parse_end, the char* store the end character of the result;
// param: require_null_terminated, check for a null terminator
parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } };
cJSON *item = NULL;
// 重置错误信息
global_error.json = NULL;
global_error.position = 0;
if (value == NULL || 0 == buffer_length)
{
goto fail;
}
// 将内容存放到buffer
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)
{
goto fail;
}
// 对buffer开始进行解析,下文会对此进行详细介绍
if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer))))
{
goto fail;
}
// 如果设置了require_null_terminated,那么需要检查末端字符是否为'\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中
*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;
}
// 将错误信息存储到return_parse_end
if (return_parse_end != NULL)
{
*return_parse_end = (const char*)local_error.json + local_error.position;
}
global_error = local_error;
}
return NULL;
}
可以看到,"cJSON_ParseWithLengthOpts()“的核心函数是"parse_value()”,在此之前,还调用了"buffer_skip_whitespace()"与"skip_utf8_bom()"函数:
-
“buffer_skip_whitespace()”
// 跳过buffer中ASI码小于等于空格的字符,这里是只跳过位于buffer开端处的字符 static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) { if ((buffer == NULL) || (buffer->content == NULL)) { return NULL; } // cannot_access_at_index检查某个是否无法访问 if (cannot_access_at_index(buffer, 0)) { return buffer; } while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) { // 持续跳过buffer中ASI码小于等于空格的字符 buffer->offset++; } if (buffer->offset == buffer->length) { buffer->offset--; } return buffer; }
-
“skip_utf8_bom()”
// 跳过缓冲区开头的UTF-8 字节顺序标注 static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) { if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) { return NULL; } if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) { // 跳过buffer前端的"\xEF\xBB\xBF"三个字符 buffer->offset += 3; } return buffer; }
"parse_value()“中根据解析对象的类型,调用了不同的子函数如"parse_string”,“parse_number”,“parse_array"与"parse_object”,这几个函数分别解析字符串,数字,json数组以及json对象。这可以根据"parse_value()"的实现方法看到:
// 字符串解析的核心公式,将buffer里面的content解析到item中去
static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer)
{
if ((input_buffer == NULL) || (input_buffer->content == NULL))
{
return false;
}
/* 解析不同类型的对象,每次解析都要改变buffer中offset,使其指向当前解析的位置 */
/* null */
if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0))
{
// null对象需要使用字符串"null"
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))
{
// false对象需要使用字符串"false"
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))
{
// ture对象需要使用字符串"true"
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] == '\"'))
{
// string对象需要以'\"'为开头,以'\"'为结束
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'))))
{
// number对象需要以'-'或者'0'~'9'为开头
return parse_number(item, input_buffer);
}
/* array */
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '['))
{
// array对象需要以'['开头,以']'结束
return parse_array(item, input_buffer);
}
/* object */
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{'))
{
// object对象需要以'{'开头,以'}'结束
return parse_object(item, input_buffer);
}
return false;
}
下面将分别分析"parse_string",“parse_number”,"parse_array"与"parse_object"的源码:
-
“parse_string”
// 用于解析字符串作为键或者值,这部分可以分成两部分来看,一部分用于找到有效字符并分配内存,一部分用于在该内存中复制字符 static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) { // input_pointer用于指向当前复制字符的位置 const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; // input_end用于指向当前字符串的结尾 const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; // output_pointer用于指向当前复制字符的目的地地址 unsigned char *output_pointer = NULL; // output用于指向复制的字符串目的地的其实地址 unsigned char *output = NULL; // 字符串需要以 '\"' 为开头,同时以 '\"' 为结束 if (buffer_at_offset(input_buffer)[0] != '\"') { goto fail; } { // 第一部分,估计有效字符串需要的内存大小,为预估计 size_t allocation_length = 0; size_t skipped_bytes = 0; while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) { // 使用input_end遍历input_buffer,找到字符串的终点 if (input_end[0] == '\\') { // 跳过取消转义字符 if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) { goto fail; } skipped_bytes++; input_end++; } input_end++; } if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) { // 如果input_end读进来的长度长于缓冲区,或者字符串不以 '\"' 结束,则发生错误 goto fail; } // allocation_length是最少应该分配的字节长度,为字符串长度减去转义字符的数量,此值是往大的估计 allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; // output指向分配的内存块 output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); if (output == NULL) { goto fail; } } output_pointer = output; while (input_pointer < input_end) { // 第二部分,循环进行复制赋值 if (*input_pointer != '\\') { // 如果不是取消转义字符 *output_pointer++ = *input_pointer++; } else { unsigned char sequence_length = 2; if ((input_end - input_pointer) < 1) { goto fail; } switch (input_pointer[1]) { case 'b': *output_pointer++ = '\b'; break; case 'f': *output_pointer++ = '\f'; break; case 'n': *output_pointer++ = '\n'; break; case 'r': *output_pointer++ = '\r'; break; case 't': *output_pointer++ = '\t'; break; case '\"': case '\\': case '/': *output_pointer++ = input_pointer[1]; break; case 'u': // 将utf16字面值转化为utf8字面值 sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); if (sequence_length == 0) { goto fail; } break; default: goto fail; } input_pointer += sequence_length; } } // 字符串后面添加 '\0' 作为结束 *output_pointer = '\0'; item->type = cJSON_String; item->valuestring = (char*)output; input_buffer->offset = (size_t) (input_end - input_buffer->content); input_buffer->offset++; return true; fail: if (output != NULL) { input_buffer->hooks.deallocate(output); } if (input_pointer != NULL) { // 将buffer中的偏移量重置 input_buffer->offset = (size_t)(input_pointer - input_buffer->content); } return false; }
-
“parse_number”
// 解析字符串中的数字 static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) { double number = 0; // 存储数字字符串转化之后的数字 unsigned char *after_end = NULL; // 指向number_c_string中字符数字的最后一位 unsigned char number_c_string[64]; // 存储有效数字的数组,可见数字最大为10E63,但后续还有个clip函数 unsigned char decimal_point = get_decimal_point(); // 获取小数点的字符 size_t i = 0; if ((input_buffer == NULL) || (input_buffer->content == NULL)) { return false; } for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) { // 逐个字节读取数字 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'; // 将字符串数字转化为浮点型数字类型 number = strtod((const char*)number_c_string, (char**)&after_end); if (number_c_string == after_end) { return false; } 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; input_buffer->offset += (size_t)(after_end - number_c_string); return true; }
-
“parse_array”
// 解析json数组,该数组以链表的形式实现 static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) { // array字符串的格式为: "[" + array + "]" cJSON *head = NULL; cJSON *current_item = NULL; if (input_buffer->depth >= CJSON_NESTING_LIMIT) { // 解析的深度大于约束值 return false; } input_buffer->depth++; if (buffer_at_offset(input_buffer)[0] != '[') { goto fail; } input_buffer->offset++; buffer_skip_whitespace(input_buffer); // 跳过左中括号后面的空格 if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) { // 如果该array为空 goto success; } if (cannot_access_at_index(input_buffer, 0)) { input_buffer->offset--; goto fail; } input_buffer->offset--; do { // 创建新的cJSON节点 cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); if (new_item == NULL) { goto fail; } if (head == NULL) { // 如果头节点为空,将新节点当作头节点 current_item = head = new_item; } else { // 如果头节点不为空,将新节点当作当前节点 current_item->next = new_item; new_item->prev = current_item; current_item = new_item; } input_buffer->offset++; buffer_skip_whitespace(input_buffer); // 递归调用parse_value if (!parse_value(current_item, input_buffer)) { goto fail; } buffer_skip_whitespace(input_buffer); } while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') { goto fail; } success: input_buffer->depth--; if (head != NULL) { head->prev = current_item; } item->type = cJSON_Array; item->child = head; input_buffer->offset++; return true; fail: if (head != NULL) { cJSON_Delete(head); } return false; }
-
“parse_object”
// 解析一个json对象,里面的元素也是以链表的形式实现 static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) { // object的字符串格式为: "{" + array + "}" cJSON *head = NULL; cJSON *current_item = NULL; if (input_buffer->depth >= CJSON_NESTING_LIMIT) { // 解析的深度大于约束值 return false; } input_buffer->depth++; if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) { goto fail; } input_buffer->offset++; buffer_skip_whitespace(input_buffer); if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) { goto success; } if (cannot_access_at_index(input_buffer, 0)) { input_buffer->offset--; goto fail; } input_buffer->offset--; do { // 创建新的节点 cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); if (new_item == NULL) { goto fail; } if (head == NULL) { // 如果头节点为空,将新节点当作头节点 current_item = head = new_item; } else { // 如果头节点不为空,将新节点当作当前节点 current_item->next = new_item; new_item->prev = current_item; current_item = new_item; } // 解析字符串作为该item的键名 input_buffer->offset++; buffer_skip_whitespace(input_buffer); if (!parse_string(current_item, input_buffer)) { goto fail; } buffer_skip_whitespace(input_buffer); // 将解析的结果当作该item的键名 current_item->string = current_item->valuestring; current_item->valuestring = NULL; // 键值对之间需要用":" if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) { goto fail; } // 解析值 input_buffer->offset++; buffer_skip_whitespace(input_buffer); if (!parse_value(current_item, input_buffer)) { goto fail; } buffer_skip_whitespace(input_buffer); } while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) { goto fail; } success: input_buffer->depth--; if (head != NULL) { head->prev = current_item; } item->type = cJSON_Object; item->child = head; input_buffer->offset++; return true; fail: if (head != NULL) { cJSON_Delete(head); } return false; }
3. 总结(Conclusion)
cJSON解析字符串的源码并不复杂,语法也比较简单,主要难点在于字符的原子处理,并且还需要考虑不同的编码方式(UTF-16 to UTF-8)。如果单纯为了使用的话,使用作者提供的接口就可以了,但是通过阅读源码,可以发现被解析的字符串的格式要求,也能学习到作者的编程思维与技巧,这些都是非常宝贵的。