引言(Introduction)
写在文章开头的一句话,怕什么真理无穷,进一步有一步的惊喜。
在第三篇的字符串解析中,介绍了cJSON的源码是如何实现解析字符串对象为一个json结构的。本文将介绍cJSON是如何实现将json结构转化为字符串的,因为该部分源码比较长,所以可能有些地方有些错误,还望纠正。
1. 实例(Example)
在分析源码之前,一样先给一个例子:
#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){
cJSON *root = NULL;
cJSON *family = NULL;
cJSON *father = NULL;
cJSON *mother = NULL;
cJSON *birthday = NULL;
char *output = NULL;
char outputBuffer[1024];
root = cJSON_CreateObject();
cJSON_AddItemToObject(root, "name", cJSON_CreateString("Zhang San"));
cJSON_AddTrueToObject(root, "sex");
cJSON_AddNumberToObject(root, "height", 1.8);
family = cJSON_AddArrayToObject(root, "family");
cJSON_AddItemToArray(family, father = cJSON_CreateObject());
cJSON_AddItemToObject(father, "name", cJSON_CreateString("Zhang Si"));
cJSON_AddItemToObject(father, "relationship", cJSON_CreateString("Father"));
cJSON_AddItemToArray(family, mother = cJSON_CreateObject());
cJSON_AddItemToObject(mother, "name", cJSON_CreateString("Li Si"));
cJSON_AddItemToObject(mother, "relationship", cJSON_CreateString("Mother"));
cJSON_AddItemToObject(root, "birthday", birthday = cJSON_CreateObject());
cJSON_AddNumberToObject(birthday, "year", 2000);
cJSON_AddNumberToObject(birthday, "month", 1);
cJSON_AddNumberToObject(birthday, "day", 1);
output = cJSON_Print(root);
printf("cJSON_Print(): \n%s\n", output);
output = cJSON_PrintUnformatted(root);
printf("cJSON_PrintUnformatted(): \n%s\n", output);
output = cJSON_PrintBuffered(root, (int)sizeof(root) + 5, 1);
printf("cJSON_PrintBuffered(): \n%s\n", output);
if(cJSON_PrintPreallocated(root, outputBuffer, 1000, 1))
printf("cJSON_PrintPreallocated(): \n%s\n", outputBuffer);
free(output);
cJSON_Delete(root);
return 0;
}
cJSON中提供了四个接口:
- cJSON_Print:将json结构转换为字符数组,字符数组中含有制表符与换行符,并返回该字符数组的指针;
- cJSON_PrintUnformated:将json结构转换为字符数组,字符数组中不含有制表符与换行符,并返回该字符数组的指针;
- cJSON_PrintBuffered:指定输出字符串长度的版本,可以选择是否按格式输出;
- cJSON_PrintPreallocated:指定输出字符串指针与输出字符串长度,可以选择是否按格式输出。
这些函数在底层实现上是相近的,因为最终都会调用到"print_value",这将会在下面的源码分析上进行介绍。
2. 源码(Source code)
下面直接给出"cJSON_Print"以及"cJSON_PrintUnformated"的源码以及其注释。
-
cJSON_Print:
// cJSON.h // 将cJSON对象转化为字符串,这是有格式的版本 CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); // cJSON.c CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) { return (char*)print(item, true, &global_hooks); }
-
cJSON_PrintUnformated:
// cJSON.h // 将cJSON对象转化为字符串,这是上面无格式的版本 CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); // cJSON.c CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) { return (char*)print(item, false, &global_hooks); }
上面两个接口都调用了"print"函数,该函数是在cJSON.c文件中定义的静态函数,只能用于该文件。
// cJSON.c
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为存储json转换过程中信息的结构体,与parsebuffer相似
printbuffer buffer[1];
// printed为最终输出的字符数组
unsigned char *printed = NULL;
memset(buffer, 0, sizeof(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;
}
// 把当前cJSON内部的信息存入到buffer中
if (!print_value(item, buffer))
{
goto fail;
}
update_offset(buffer);
if (hooks->reallocate != NULL)
{
printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1);
if (printed == NULL) {
goto fail;
}
buffer->buffer = NULL;
}
else
{
printed = (unsigned char*) hooks->allocate(buffer->offset + 1);
if (printed == NULL)
{
goto fail;
}
// 将buffer中的信息复制到printed中
// #define cjson_min(a, b) (((a) < (b)) ? (a) : (b))
memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1));
printed[buffer->offset] = '\0';
hooks->deallocate(buffer->buffer);
}
return printed;
fail:
if (buffer->buffer != NULL)
{
hooks->deallocate(buffer->buffer);
}
if (printed != NULL)
{
hooks->deallocate(printed);
}
return NULL;
}
上示源码中,使用到了"printbuffer"结构体存储字符串信息,其内容为:
// cJSON.c
// 用于将cJSON转化为字符串的结构体
typedef struct
{
unsigned char *buffer; // 用于存放字符串
size_t length; // buffer的长度
size_t offset; // 输入指针在buffer中距离开端的偏移量
size_t depth; // 表示json结构体中嵌套的深度
cJSON_bool noalloc; // 表示是否需要分配重新分配内存
cJSON_bool format; // 表示是否按照格式输出字符串
internal_hooks hooks; // 内存分配函数
} printbuffer;
另外两个接口分别为:
-
cJSON_PrintBuffered:
// cJSON.h // 这是使用指定buffer大小策略的Print版本,可以根据fmt选择是否具有格式。 CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); // cJSON.c CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) { printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; if (prebuffer < 0) { return NULL; } // 预先按照指定大小分配内存 p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); if (!p.buffer) { return NULL; } p.length = (size_t)prebuffer; p.offset = 0; p.noalloc = false; // 指明会对buffer进行扩容 p.format = fmt; p.hooks = global_hooks; if (!print_value(item, &p)) { global_hooks.deallocate(p.buffer); return NULL; } return (char*)p.buffer; }
-
cJSON_PrintPreallocated:
// cJSON.h // 使用指定buffer的版本,解析的字符串会写入到指定的buffer中 // NOTE: cJSON在预用内存的估计上并不是百分百准确的,所以可以多分配5个字节的内存 CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); // cJSON.c CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) { printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; if ((length < 0) || (buffer == NULL)) { return false; } // 将输出的数组指向指定的buffer p.buffer = (unsigned char*)buffer; p.length = (size_t)length; p.offset = 0; p.noalloc = true; // 不支持对buffer扩容 p.format = format; p.hooks = global_hooks; return print_value(item, &p); }
可以发现,上面四个接口最终都调用了"print_value"函数,该函数的源码如下:
// 将cJSON对象转化为字符串的实际操作函数
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:
// ensure函数为output_buffer分配多余的内存
// 为"null"字符串在最后添加5字节的空间,预留一个空字节的空间
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:
// 将数值型对象转化为字符串型,因为存在double型的值,所以需要将其小数点位置也找出来
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;
}
}
这里面涉及到了扩容函数"ensure",打印数字"print_number",打印字符串"print_string",打印数组"print_array"以及打印对象"print_object"的函数。
扩容函数"ensure"主要是为了确保buffer中还存在足够的空余空间,其函数实现如下:
// 检查p中的buffer是否还具有needed大小的空间,如果空余空间不足,则需要分配多余的内存(noalloc为false)
static unsigned char* ensure(printbuffer * const p, size_t needed)
{
// newbuffer为新的buffer空间
unsigned char *newbuffer = NULL;
size_t newsize = 0;
if ((p == NULL) || (p->buffer == NULL))
{
return NULL;
}
if ((p->length > 0) && (p->offset >= p->length))
{
return NULL;
}
if (needed > INT_MAX)
{
return NULL;
}
// 在当前偏移量的基础上判断是否需要分配更多的内存
needed += p->offset + 1;
if (needed <= p->length)
{
// 不需要分配更多的内存
return p->buffer + p->offset;
}
if (p->noalloc) {
// 判断是否能够分配内存给buffer
return NULL;
}
// 后续代码实现分配两倍needed或者INT_MAX大小的内存
if (needed > (INT_MAX / 2))
{
// INT_MAX/2 < needed <= INT_MAX/2时分配INT_MAX大小内存
if (needed <= INT_MAX)
{
newsize = INT_MAX;
}
else
{
return NULL;
}
}
else
{
// needed <= INT_MAX/2时分配needed大小内存
newsize = needed * 2;
}
if (p->hooks.reallocate != NULL)
{
// 如果存在内存重分配函数
newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize);
if (newbuffer == NULL)
{
p->hooks.deallocate(p->buffer);
p->length = 0;
p->buffer = NULL;
return NULL;
}
}
else
{
// 如果不存在内存重分配函数
newbuffer = (unsigned char*)p->hooks.allocate(newsize);
if (!newbuffer)
{
p->hooks.deallocate(p->buffer);
p->length = 0;
p->buffer = NULL;
return NULL;
}
memcpy(newbuffer, p->buffer, p->offset + 1);
p->hooks.deallocate(p->buffer);
}
p->length = newsize;
p->buffer = newbuffer;
return newbuffer + p->offset;
}
打印各种类型的函数的源码依次为:
-
print_number:
// 将数字转化为字符串 static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) { unsigned char *output_pointer = NULL; // 指向output_buffer中的转化结果 double d = item->valuedouble; // 需要转换的数字 int length = 0; size_t i = 0; // 迭代下标 unsigned char number_buffer[26] = {0}; // 临时存储数字字符的数组 unsigned char decimal_point = get_decimal_point(); // 获取小数点的字符表达 double test = 0.0; // number_buffer中的数字 if (output_buffer == NULL) { return false; } // 检查需要打印的数字是否是nan或者inf if (isnan(d) || isinf(d)) { length = sprintf((char*)number_buffer, "null"); } else { // 检查d的小数点后15位,避免不必要的空间 length = sprintf((char*)number_buffer, "%1.15g", d); // 检查number_buffer中的数字是否能够表示d if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) { // 如果不能,则使用小数点后17位表示d length = sprintf((char*)number_buffer, "%1.17g", d); } } // 转换错误或者发生溢出 if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) { return false; } // 为output_buffer分配内存 output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); if (output_pointer == NULL) { return false; } for (i = 0; i < ((size_t)length); i++) { // 利用output_pointer将number_buffer中的字符一一复制到output_buffer中 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)length; return true; }
-
print_string:
print_string中调用了"print_string_ptr"函数,因为在"print_string_ptr"中能够实现键名的打印与键值字符串的打印功能。// 调用print_string_ptr打印键名 static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) { return print_string_ptr((unsigned char*)item->valuestring, p); }
print_string_ptr的源码如下:
// 打印字符串,需要处理转义字符 static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) { const unsigned char *input_pointer = NULL; // 打印字符的遍历指针 unsigned char *output = NULL; // 指向output_buffer中buffer的末尾地址 unsigned char *output_pointer = NULL; // 输出字符的遍历指针 size_t output_length = 0; // 需要申请的多余空间 size_t escape_characters = 0; // 需要跳过的字节数,在处理取消转义字符时用到 if (output_buffer == NULL) { return false; } if (input == NULL) { // 如果字符串为NULL,那么输出为"\"\"" output = ensure(output_buffer, sizeof("\"\"")); if (output == NULL) { return false; } strcpy((char*)output, "\"\""); return true; } for (input_pointer = input; *input_pointer; input_pointer++) { // 利用input_pointer遍历一遍input字符串 switch (*input_pointer) { case '\"': case '\\': case '\b': case '\f': case '\n': case '\r': case '\t': // 一个字符的转义序列,扩充一个字节 escape_characters++; break; default: if (*input_pointer < 32) { // 特殊字符的转义需要需要扩充五个字节 escape_characters += 5; } break; } } // 计算需要申请的多余空间 output_length = (size_t)(input_pointer - input) + escape_characters; // 扩容 output = ensure(output_buffer, output_length + sizeof("\"\"")); if (output == NULL) { return false; } if (escape_characters == 0) { // 没有转义字符存在的情况 output[0] = '\"'; memcpy(output + 1, input, output_length); output[output_length + 1] = '\"'; output[output_length + 2] = '\0'; return true; } output[0] = '\"'; output_pointer = output + 1; // 复制字符串到output for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) { if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) { // 普通字符 *output_pointer = *input_pointer; } else { // 需要转义的字符,先在前面加上'\\' *output_pointer++ = '\\'; switch (*input_pointer) { case '\\': *output_pointer = '\\'; break; case '\"': *output_pointer = '\"'; break; 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; default: // 特殊字符需要编码为unicode的格式 sprintf((char*)output_pointer, "u%04x", *input_pointer); output_pointer += 4; break; } } } output[output_length + 1] = '\"'; output[output_length + 2] = '\0'; // 字符串的输出都是 \" + string + \" + \0 的格式 return true; }
-
print_array:
// 将json数组转化为字符串 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; // 通过链表遍历json数组 if (output_buffer == NULL) { return false; } // 为"["字符确保一个字节的空间 output_pointer = ensure(output_buffer, 1); if (output_pointer == NULL) { return false; } *output_pointer = '['; output_buffer->offset++; output_buffer->depth++; while (current_element != NULL) { // 遍历array中的数据 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--; // 最终的结果是 [ + array + ] + \0 return true; }
-
print_object:
// 将一个object对象转换为字符串 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; } // 为"{"与"\n"分配空间 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++) { // 每增加一层深度,添加一个制表符'\t' *output_pointer++ = '\t'; } output_buffer->offset += output_buffer->depth; } // 转化键名 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; } *output_pointer++ = ':'; if (output_buffer->format) { *output_pointer++ = '\t'; } output_buffer->offset += length; // 打印键值 if (!print_value(current_item, output_buffer)) { return false; } update_offset(output_buffer); // 确保一个","或者"\n",与下一个item的大小的空间 length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); output_pointer = ensure(output_buffer, length + 1); if (output_pointer == NULL) { return false; } if (current_item->next) { *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--; // 输出对象的格式为 { + cJSON object + } + \0 return true; }
总结(Conclusion)
cJSON转化json结构为字符串时,需要分别针对不同类型的键值进行分支处理,每种情况需要的字节数不同。同时处理的逻辑也不尽相同,如处理数字时需要判断该数字大小,处理object对象时需要判断深度添加制表符等。从源码中学习,不断成长。