cJSON 源码解析:parse_string 函数深入剖析
1. 引言cJSON 是一个轻量级的 C 语言 JSON 解析库因其代码简洁、无外部依赖而广受欢迎。在 cJSON 的解析流程中parse_string函数负责将 JSON 字符串字面量如hello、a\\nb解析为 C 字符串是整个解析器中最核心、也最容易出错的环节之一。本文将逐行剖析 cJSON 源码中的parse_string实现讲解其转义字符处理、Unicode 解码、内存分配等关键细节帮助你彻底理解这个函数的工作原理。2. parse_string 函数概览parse_string函数定义在cJSON.c文件中其函数签名如下staticcJSON_boolparse_string(cJSON*item,constunsignedchar*constinput,constunsignedchar**ep);三个参数的含义item指向待填充的 cJSON 节点解析成功后字符串值会存入item-valuestring。input指向 JSON 文本中字符串的起始位置即开头的双引号。ep指向指针的指针解析失败时用于记录错误位置。函数返回cJSON_bool即int成功返回true失败返回false。3. 整体流程parse_string的整体流程可以概括为以下几个步骤否是检查开头引号是否含转义字符?直接计算长度并复制逐字符解析转义处理 Unicode 转义分配内存并填充写入 valuestring更新解析位置 ep下面我们按照源码顺序逐步拆解每个环节。4. 源码逐段解析4.1 前置检查确认起始引号if(inputNULL||*input!\){returnfalse;}函数首先检查input是否为空以及当前位置是否为双引号。如果不是直接返回false表示解析失败。这是字符串解析的入口校验。4.2 第一遍扫描计算字符串长度constunsignedchar*pointerinput1;size_tlength0;while(*pointer!\*pointer!\0){if(*pointer\\){pointer;if(*pointer\0){returnfalse;}pointer;}else{pointer;}length;}这一遍扫描的目的是计算字符串的实际长度不含转义符本身。注意遇到\时跳过转义符及其后面的一个字符如\n、\都算一个字符。遇到或字符串结束符\0时停止。如果遇到\后紧跟\0说明转义不完整返回false。这里有个细节length统计的是转义后的字符数而不是原始字节数。例如a\\nb的原始字节数是 5a、\、n、b、但转义后实际只有 3 个字符a、换行、b。4.3 判断是否需要二次扫描if(*pointer!\){returnfalse;}第一遍扫描结束后pointer应指向结束引号。如果不是说明字符串没有正常闭合返回false。if(pointerinput1){/* 空字符串 */item-valuestring(char*)cJSON_malloc(1);if(item-valuestringNULL){returnfalse;}item-valuestring[0]\0;*eppointer1;returntrue;}如果pointer input 1说明字符串是空的直接分配 1 字节内存存放\0即可。if(length(size_t)(pointer-input)-1){/* 没有转义字符直接复制 */item-valuestring(char*)cJSON_malloc(length1);if(item-valuestringNULL){returnfalse;}memcpy(item-valuestring,input1,length);item-valuestring[length]\0;*eppointer1;returntrue;}这里是一个性能优化如果length不小于原始字节数减一即没有转义字符被压缩说明字符串中没有转义序列可以直接用memcpy批量复制无需逐字符处理。4.4 第二遍扫描处理转义字符/* 有转义字符需要逐字符处理 */pointerinput1;unsignedchar*output(unsignedchar*)cJSON_malloc(length1);if(outputNULL){returnfalse;}while(*pointer!\*pointer!\0){if(*pointer!\\){*output*pointer;}else{pointer;switch(*pointer){caseb:*output\b;break;casef:*output\f;break;casen:*output\n;break;caser:*output\r;break;caset:*output\t;break;case\:*output\;break;case\\:*output\\;break;case/:*output/;break;caseu:/* Unicode 转义稍后详解 */...default:cJSON_free(output);returnfalse;}pointer;}}第二遍扫描逐字符处理普通字符直接复制。遇到\时根据后面的字符进行转义映射。支持的标准转义\b、\f、\n、\r、\t、\、\\、\/、\uXXXX。遇到未知转义如\x释放内存并返回false。4.5 Unicode 转义处理caseu:/* 解析 4 位十六进制 Unicode 码点 */if(parse_hex4(pointer1,uc)){/* 处理代理对 */if((uc0xD800)(uc0xDBFF)(*(pointer5)\\)(*(pointer6)u)){unsignedshortuc20;if(parse_hex4(pointer7,uc2)){if((uc20xDC00)(uc20xDFFF)){/* 计算最终 Unicode 码点 */unsignedlongcp((uc-0xD800)10)(uc2-0xDC00)0x10000;/* 编码为 UTF-8 */...}}}/* 将码点编码为 UTF-8 字节序列 */...}break;Unicode 转义是parse_string中最复杂的部分涉及解析 4 位十六进制parse_hex4读取\uXXXX中的 4 个十六进制字符。代理对处理如果码点在0xD800~0xDBFF高代理区且后面紧跟\u则继续读取低代理区的 4 位十六进制组合成完整的 Unicode 码点范围0x10000~0x10FFFF。UTF-8 编码将 Unicode 码点转换为 1~4 字节的 UTF-8 序列。4.6 收尾写入 valuestring*output\0;item-valuestring(char*)output;*eppointer1;returntrue;第二遍扫描结束后在输出末尾写入\0将output赋值给item-valuestring并更新ep指向结束引号之后的位置返回成功。5. 关键细节与注意事项5.1 内存分配策略parse_string采用两遍扫描策略第一遍只统计长度不分配内存。第二遍才分配内存并填充。这样做的目的是精确分配内存避免过度分配或二次扩容。对于没有转义的字符串直接用memcpy一次复制性能更高。5.2 错误处理函数在以下情况返回false输入为空或开头不是引号。字符串未闭合缺少结束引号。转义序列不完整\后紧跟\0。未知的转义字符。内存分配失败。每次失败前都会释放已分配的内存避免内存泄漏。5.3 与 parse_value 的协作parse_string通常由parse_value调用。当parse_value遇到时会调用parse_string来解析字符串值case\:returnparse_string(item,input,ep);解析成功后item-type会被设置为cJSON_Stringitem-valuestring指向解析出的字符串。6. 完整源码精简注释版staticcJSON_boolparse_string(cJSON*item,constunsignedchar*constinput,constunsignedchar**ep){constunsignedchar*pointerinput1;size_tlength0;if(inputNULL||*input!\){returnfalse;}/* 第一遍计算长度 */while(*pointer!\*pointer!\0){if(*pointer\\){pointer;if(*pointer\0){returnfalse;}pointer;}else{pointer;}length;}if(*pointer!\){returnfalse;}if(pointerinput1){/* 空字符串 */item-valuestring(char*)cJSON_malloc(1);if(item-valuestringNULL){returnfalse;}item-valuestring[0]\0;*eppointer1;returntrue;}if(length(size_t)(pointer-input)-1){/* 无转义直接复制 */item-valuestring(char*)cJSON_malloc(length1);if(item-valuestringNULL){returnfalse;}memcpy(item-valuestring,input1,length);item-valuestring[length]\0;*eppointer1;returntrue;}/* 第二遍处理转义 */pointerinput1;unsignedchar*output(unsignedchar*)cJSON_malloc(length1);if(outputNULL){returnfalse;}while(*pointer!\*pointer!\0){if(*pointer!\\){*output*pointer;}else{pointer;switch(*pointer){caseb:*output\b;break;casef:*output\f;break;casen:*output\n;break;caser:*output\r;break;caset:*output\t;break;case\:*output\;break;case\\:*output\\;break;case/:*output/;break;caseu:/* Unicode 处理省略细节 */break;default:cJSON_free(output);returnfalse;}pointer;}}*output\0;item-valuestring(char*)output;*eppointer1;returntrue;}7. 总结parse_string是 cJSON 解析器中处理字符串的核心函数其设计体现了几个重要思想两遍扫描先统计长度再分配内存兼顾效率与精确性。无转义快路径对不含转义的字符串使用memcpy批量复制避免逐字符开销。完整的转义支持覆盖 JSON 规范定义的所有标准转义并正确处理 Unicode 代理对。严谨的错误处理每个失败路径都释放内存避免泄漏。理解parse_string的实现不仅能帮助你深入掌握 cJSON 的工作原理也能为阅读其他 JSON 解析库如 yyjson、jansson的字符串处理逻辑打下良好基础。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →