赞
踩
cJSON系列:
继上一章之后,这章讲json的打印。json存在的目的是为了数据交换的方便,而数据交换就比较通过传输来实现,json在互联网上的传输是以字符串的形式进行的,这就需要将cjson中一个个结构体打印成标准的json的字符串形式。
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
注意,返回的字符串指针是通过动态内存分配的,使用完成后应该释放内存。
/*
* 将一个json结构体及其子json输出为字符串
* note:使用完字符串后要记得释放内存
*/
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item)
{
return (char*)print(item, true, &global_hooks);
}
其实调用的是print()函数,其中的format参数为true,说明默认是格式化输出(效果就是有换行,TAB等特殊符号):
/*
* 将json转为字符串
* format:true:格式化打印
* 返回:字符串
*/
static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks)
{
//默认print_buff长度,需要根据json结构体修改
static const size_t default_buffer_size = 256;
printbuffer buffer[1]; //用于存储输出字符串
unsigned char *printed = NULL;
//内存初始为0
memset(buffer, 0, sizeof(buffer));
//创建默认的buff
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;
}
//打印item的内容到buffer
if (!print_value(item, buffer))
{
goto fail;
}
//更新buffer的指针偏移
update_offset(buffer);
//重新分配print_buffer的缓存,并将指针赋值给printed
if (hooks->reallocate != NULL)
{
//reallocate在原指针上进行再分配
printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1);
if (printed == NULL) {
goto fail;
}
buffer->buffer = NULL;
}
else /* otherwise copy the JSON over to a new buffer */
{
//系统不支持reallocate,给指针printed重新分配内存,复制并释放buffer->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;
}
这里需要认识一下printbuff结构体
该结构体用于辅助打印json:
//缓存json的输出内容
typedef struct
{
unsigned char *buffer; //存放结果字符串
size_t length; //内存总长度
size_t offset; //字符串长度
size_t depth; //嵌套的层数/* current nesting depth (for formatted printing) */
cJSON_bool noalloc;
cJSON_bool format; /* is this print a formatted print */
internal_hooks hooks;
} printbuffer;
另外代码中出现了两个比较重要的函数:print_value()
,update_offset()
接下来将详解他们;
/*
* 打印item内容到output_buffer
* 成功:true
*/
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) & 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);
//若是原生的json字符串数据,则直接取item->valuestring
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;
}
//打印不同item的内容
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;
}
}
print_value() 只打印item的键-值
内容,因为不同类型的item,打印内容的逻辑不一样,所以在这个函数中,对不同类型的item作了不同的处理。
我们主要分析打印字符串,数组与object类型的item的代码:
首先另一个同样重要的函数,ensure();人如其名,该函数的作用是确保buff的内存够用,并返回buffer的可用内存的指针。每次给printbuffer赋值前,都需要计算所需的内存,并调用ensure()确保内存够用。 分析如下:
/*
* 检查printbuffer的缓存区与needed的大小,确保所需内存足够
* 返回当前buffer的可用内存的指针
*/
static unsigned char* ensure(printbuffer * const p, size_t needed)
{
unsigned char *newbuffer = NULL;
size_t newsize = 0;
if ((p == NULL) || (p->buffer == NULL))
{
return NULL;
}
//offset此时应该是0,确保offset的正确性
if ((p->length > 0) && (p->offset >= p->length))
{
/* make sure that offset is valid */
return NULL;
}
//限制缓存大小
if (needed > INT_MAX)
{
/* sizes bigger than INT_MAX are currently not supported */
return NULL;
}
//所需内存是offset+1这个1是放字符串结束符
needed += p->offset + 1;
//所需要的内存足够用,返回可用内存的起点
if (needed <= p->length)
{
return p->buffer + p->offset;
}
//否则内存不够,需要重新分配,检查分配函数是否存在
if (p->noalloc) {
return NULL;
}
/* calculate new buffer size */
//计算所需要的内存,限制规模
if (needed > (INT_MAX / 2))
{
/* overflow of int, use INT_MAX if possible */
if (needed <= INT_MAX)
{
newsize = INT_MAX;
}
else
{
return NULL;
}
}
else
{
newsize = needed * 2;
}
//reallocate在原指针上重新分配内存
if (p->hooks.reallocate != NULL)
{
/* reallocate with realloc if available */
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
{
/* otherwise reallocate manually */
//系统不支持reallocate,使用allocate创建新的指针,再分配内存
newbuffer = (unsigned char*)p->hooks.allocate(newsize);
if (!newbuffer)
{
p->hooks.deallocate(p->buffer);
p->length = 0;
p->buffer = NULL;
return NULL;
}
//给new_buff复制旧buff的内容
if (newbuffer)
{
memcpy(newbuffer, p->buffer, p->offset + 1);
}
//再释放旧buff的内存
p->hooks.deallocate(p->buffer);
}
//给p装上新的buff
p->length = newsize;
p->buffer = newbuffer;
return newbuffer + p->offset;
}
打印字符串类型的item,如:
“name”: “fool”
//将item中的数据复制到printbuffer
static cJSON_bool print_string(const cJSON * const item, printbuffer * const p)
{
return print_string_ptr((unsigned char*)item->valuestring, p);
}
函数的作用是将string类型的item里的字符串值打印到printbuff,实际实现是以下函数print_string_ptr(),它的作用是打印字符串指针。
/*
* 打印字符串
* 例如input的内容可能是 "string",其中的双引号需要特殊处理
*/
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;
unsigned char *output_pointer = NULL;
size_t output_length = 0;
/* numbers of additional characters needed for escaping */
//需要补充字节数
size_t escape_characters = 0;
if (output_buffer == NULL)
{
return false;
}
/* empty string */
if (input == NULL)
{
//输出为空串
output = ensure(output_buffer, sizeof("\"\""));
if (output == NULL)
{
return false;
}
strcpy((char*)output, "\"\"");
return true;
}
/* set "flag" to 1 if something needs to be escaped */
//检查输入字符串中的特殊字符,如果是换行符'\n',则输出需要两个字符:'\'和'n'
for (input_pointer = input; *input_pointer; input_pointer++)
{
switch (*input_pointer)
{
case '\"':
case '\\':
case '\b': //退格(BS) ,将当前位置移到前一列
case '\f': //换页(FF),将当前位置移到下页开头
case '\n': //换行(LF) ,将当前位置移到下一行开头
case '\r': //回车(CR) ,将当前位置移到本行开头
case '\t': //水平制表(HT)
/* one character escape sequence */
//需要额外一个字符
escape_characters++;
break;
default:
if (*input_pointer < 32)
{
/* UTF-16 escape sequence uXXXX */
//需要额外5个字符
escape_characters += 5;
}
break;
}
}
//输出的长度是输入长度+额外的字节
output_length = (size_t)(input_pointer - input) + escape_characters;
//确保输出buff足够大
output = ensure(output_buffer, output_length + sizeof("\"\""));
if (output == NULL)
{
return false;
}
//无额外字节(无转义字符)时直接复制字符串 记得带上双引号和结束符
/* no characters have to be escaped */
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;
/* copy the string */
for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++)
{
if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\'))
{
/* normal character, copy */
//普通的字符直接复制
*output_pointer = *input_pointer;
}
else //特殊字符
{
/* character needs to be escaped */
//需要补充的字符:主要是转义字符
*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:
/* escape and print as unicode codepoint */
sprintf((char*)output_pointer, "u%04x", *input_pointer);
output_pointer += 4;
break;
}
}
}
output[output_length + 1] = '\"';
output[output_length + 2] = '\0';
return true;
}
需要关注的是特殊字符的处理,例如换行符LF
的ascii码为0x0A,在内存中占一个字节,转换为字符串时为\n
占有两个字符。
这也是该函数与字符串复制函数strcpy不同的地方。
打印数字字符串
/*
* 将数字转成字符串 赋值到printbuffer
*/
static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer)
{
unsigned char *output_pointer = NULL;
double d = item->valuedouble; //获取item的数值
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 */
//检查json的数值是否无穷大
if ((d * 0) != 0)
{
//在这里是将null赋值给number_buffer,表示该数值不可信
length = sprintf((char*)number_buffer, "null");
}
else
{
//先尝试小数点后15位的精度打印
/* 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 */
//如果无法还原,说明转换精度不够,提高到小数点后17位
length = sprintf((char*)number_buffer, "%1.17g", d);
}
}
/* sprintf failed or buffer overrun occurred */
//检查number_buffer是否正常赋值或者溢出
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 '.' */
//将number_buffer复制到output_pointer中,并添加小数点
for (i = 0; i < ((size_t)length); i++)
{
if (number_buffer[i] == decimal_point)
{
output_pointer[i] = '.';
continue;
}
output_pointer[i] = number_buffer[i];
}
//加入字符串结束符,更新字符串长度offset
output_pointer[i] = '\0';
output_buffer->offset += (size_t)length;
return true;
}
sprintf();是c语言自带的格式化输出函数。
打印数组
/* 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. */
//因为是数组所以给字符串先赋值一个[
output_pointer = ensure(output_buffer, 1);
if (output_pointer == NULL)
{
return false;
}
*output_pointer = '[';
output_buffer->offset++;
//嵌套层数计+1(因为进入了child)
output_buffer->depth++;
//从头打印数组里的子元素
while (current_element != NULL)
{
//打印一个子元素
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++ = ' ';
}
//'\0'后续会被覆盖掉,因为offset在它之前
*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;
}
打印json对象
/*
* 打印json数据到output_buffer
*/
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; //object对象下的第一个item
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;
}
//json必须有大括号
*output_pointer++ = '{';
//嵌套层数+1
output_buffer->depth++;
if (output_buffer->format)
{
//格式化输出带换行符
*output_pointer++ = '\n';
}
//更新offset
output_buffer->offset += length;
//打印该json所有的item
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;
}
/* print key */
//打印item的键
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++ = ':';
//oh,格式化输出,冒号后还要一个制表符
if (output_buffer->format)
{
*output_pointer++ = '\t';
}
output_buffer->offset += length;
/* print value */
//打印item的内容
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) + (size_t)(current_item->next ? 1 : 0));
output_pointer = ensure(output_buffer, length + 1);
if (output_pointer == NULL)
{
return false;
}
//如果还有item,则用逗号隔开,最后一个item结尾不用逗号
if (current_item->next)
{
*output_pointer++ = ',';
}
if (output_buffer->format)
{
//格式化输出需要换行
*output_pointer++ = '\n';
}
//给output_pointer一个字符串结束符,outputbuff不会受到影响
*output_pointer = '\0';
output_buffer->offset += length;
//打印下一个item
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;
}
如果你足够细心就会发现,每次调用print_value()
后都要更新offset,这是由于print_value()会将item打印到printbuffer里,但不会更新offset,而是在printbuffer后加了字符串结束符\0
。update_offset()就是以此来更新offset。
/*
* 计算printbuffer里的字符串长度,并由此更新printbuffer的offset,调用print_value()都要更新
*/
static void update_offset(printbuffer * const buffer)
{
const unsigned char *buffer_pointer = NULL;
if ((buffer == NULL) || (buffer->buffer == NULL))
{
return;
}
//获取当前offset指向的地址
buffer_pointer = buffer->buffer + buffer->offset;
//计算新的字符串长度并加到offset
buffer->offset += strlen((const char*)buffer_pointer);
}
非格式化打印,效果如下:
1, CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
在这里插入代码片
prebuffer:指定打印输出buffer的大小;
fmt:格式化输出;
2, CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt)
由外部代码申请的内存来存放打印的数据。
buf:存放打印结构的内存;
len:内存大小;
fmt:格式化输出;
CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buf, const int len, const cJSON_bool fmt)
通过本章打印字符串的学习,对cjson结构体与json字符串之间转换加深理解,对于接下来的逆过程:json字符串转cjson结构体的理解会有更大的帮助。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。