cJSON Note(4):转换字符串
文章目录
- 引言(Introduction)
- 1. 实例(Example)
- 2. 源码(Source code)
- 总结(Conclusion)
- 参考资料(Reference)
引言(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;}// 将输出的数组指向指定的bufferp.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) {// 判断是否能够分配内存给bufferreturn 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或者infif (isnan(d) || isinf(d)){length = sprintf((char*)number_buffer, "null");}else{// 检查d的小数点后15位,避免不必要的空间length = sprintf((char*)number_buffer, "%1.15g", d);// 检查number_buffer中的数字是否能够表示dif ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)){// 如果不能,则使用小数点后17位表示dlength = 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;// 复制字符串到outputfor (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 + ] + \0return 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 + } + \0return true; }
总结(Conclusion)
cJSON转化json结构为字符串时,需要分别针对不同类型的键值进行分支处理,每种情况需要的字节数不同。同时处理的逻辑也不尽相同,如处理数字时需要判断该数字大小,处理object对象时需要判断深度添加制表符等。从源码中学习,不断成长。
参考资料(Reference)
cjson-sourceforge
cjson-github
cJSON Note(4):转换字符串相关推荐
- python整数转换字符串_Python | 将字符串转换为整数列表
python整数转换字符串 Given a string with digits and we have to convert the string to its equivalent list of ...
- c语言uppercase恢复小写,C语言转换字符串为大写和小写
下面是编程之家 jb51.cc 通过网络收集整理的代码片段. 编程之家小编现在分享给大家,也给大家做个参考. #include /* * Convert a string to lowercase * ...
- python整数转换字符串_使用Python中的str()函数将整数值转换为字符串
python整数转换字符串 Given an integer value and we have to convert the value to the string using str() func ...
- 2027. 转换字符串的最少操作次数
2027. 转换字符串的最少操作次数 给你一个字符串 s ,由 n 个字符组成,每个字符不是 'X' 就是 'O' . 一次 操作 定义为从 s 中选出 三个连续字符 并将选中的每个字符都转换为 'O ...
- 在Linux下使用iconv转换字符串编码
在Linux下写C程序,尤其是网络通信程序时经常遇到编码转换的问题,这里要用到iconv函数库. iconv函数库有以下三个函数 1 2 3 4 5 6 #include <iconv.h> ...
- mysql中转换成字符串_如何在R中转换字符串的大小写?
mysql中转换成字符串 Hello, folks. In this tutorial we are going to convert the case of the string in R. The ...
- sql to_char 日期转换字符串
sql to_char 日期转换字符串 1.转换函数 与date操作关系最大的就是两个转换函数:to_date(),to_char() to_date() 作用将字符类型按一定格式转化为日期类型: 具 ...
- 摩尔斯电码转换python编码_python转换字符串为摩尔斯电码的方法
python转换字符串为摩尔斯电码的方法 本文实例讲述了python转换字符串为摩尔斯电码的方法.分享给大家供大家参考.具体实现方法如下: chars = ",.0123456789?abc ...
- 对象转换字符串格式的JSON
开发工具与关键技术:Eclipse 10.java 作者:梁添荣 撰写时间:2020-04-28 有时我们传到页面的json数据,如果有日期格式,则不会以我们想要的格式去输出,这是我们可以自定义工具, ...
最新文章
- java 证件识别_证件识别接口JAVA调用示例
- F#探险之旅(三):命令式编程(上)
- ce修改器传奇刷元宝_真原始传奇刷元宝方法 不封号刷元宝技巧
- Echarts 解决饼图文字过长重叠的问题
- 4、通过uiautomatorviewer实现appium元素定位
- mysql不同服务器数据库查询_不同服务器不同数据库两张表连接查询使用经验
- apache dubbo 自定义全局统一的异常处理器
- 局域网ARP协议和欺骗技术及其对策
- Oracle Awr
- [从零开始学习FPGA编程-16]:快速入门篇 - 操作步骤2-4- Verilog HDL语言描述语言基本语法(软件程序员和硬件工程师都能看懂)
- ps基本操作以及常用快捷键
- 利用接口和继承实现 求三角形 圆形面积 和以圆形为底的圆锥形的体积
- 澳洲PHP工作,怀爱伦澳洲行_在新西兰的工作
- 卫星影像0.3米到2米精度样例参照图
- 关于提升短信ROI,我的6点思考
- java八皇后答案_java八皇后问题详解
- WindowsPE无法安装系统
- VBA实现xls批量转换为xlsx(非新增副本文件)
- 你知道管理工作中要远离三只猫吗?
- 如何批量修改文件后缀名?(批量修改文件的扩展名)
热门文章
- 职称计算机考试输入破折号,2015职称计算机考试Dreamweaver考前测试题及答案
- Splitter Control for Dialog
- Office Visio简介
- 人工智能(网络爬虫)
- react脚手架创建项目报错,ReactDOM.render is no longer supported in React 18.
- 村庄规划gis基础操作详细步骤
- Ubuntu 16.04通过无线网卡使用桥接模式上网
- 抑制剧毒弧菌的新型噬菌体被发现
- AS400 - DB2 for i的加密、解密
- 纸上得来终觉浅,绝知此事要躬行——Spring boot任务调度