跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C

cJSON 1.7.19 源码剖析:数据结构与解析实现

cJSON 是轻量级 C 语言 JSON 库,核心数据结构是一个 cJSON 结构体,通过 next/prev 和 child 构建树状链表表示各类 JSON 值。类型采用位掩码设计,可组合附加标记。解析入口 cJSON_Parse 调用 parse_value 根据首字符递归分派至各种类型,数组和对象的解析受嵌套深度限制。生成时由 cJSON_Print 调用 print_value 按类型输出字符串,缓冲区动态扩容。文章还梳理了内存安全策略、可插拔分配器、locale 处理等设计特点,并提供了带注释的源码副本和测试方法,适合 C 开发者理解及二次开发。

鲜活发布于 2026/6/11更新于 2026/8/2017 浏览
cJSON 1.7.19 源码剖析:数据结构与解析实现

cJSON 大概是用得最广的 C 语言 JSON 库之一,两个文件、无依赖、MIT 协议,嵌入式项目里到处能见到它。最近因为给项目加注释,顺便把它的数据结构和解析流程完整走了一遍,记在这里。

cJSON 结构体

所有 JSON 值——不管是对象、数组、字符串还是数字——都用同一个结构体表示,定义在 cJSON.h 里:

typedef struct cJSON {
    struct cJSON* next;     /* 同级下一个兄弟节点 */
    struct cJSON* prev;     /* 同级上一个兄弟节点 */
    struct cJSON* child;    /* 第一个子节点(仅 Array/Object 使用) */
    int type;               /* 节点类型(位掩码) */
    char* valuestring;      /* 字符串值(String/Raw 类型) */
    int valueint;           /* 整数值(已废弃,建议用 valuedouble) */
    double valuedouble;     /* 数字值(Number 类型) */
    char* string;           /* 键名(仅当节点是对象的子项时有效) */
} cJSON;

一个节点同时干两件事:表示一个值,也链接着同级的兄弟和下一级的子节点。next/prev 组成双向链表,child 指向第一个子节点。对象里的键名存在 string 字段里,字符串值存在 valuestring,数字用 valuedouble,valueint 已经标记为废弃了。

也就是说,整棵 JSON 树就是一棵用 next/prev 和 child 串起来的树状链表。

在 64 位系统上,一个节点大约占 64 字节(对齐后),具体布局如下:

[图示:64 位系统下 cJSON 结构体内存偏移]

偏移 (字节)大小成员
08 字节next
88 字节prev
168 字节child
244 字节type
284 字节(padding)
328 字节valuestring
404 字节valueint
444 字节(padding)
488 字节valuedouble
568 字节string

32 位下因为指针短,大概 40 字节左右。

类型没有用 enum,而是用位掩码定义在低 8 位:cJSON_False 是 1,True 是 2,Number 是 8,String 是 16,Array 是 32,Object 是 64,Raw 是 128。这样一来,还可以在高位上附加标记,比如 cJSON_IsReference (256) 和 cJSON_StringIsConst (512),用来控制释放行为。要取纯类型就 type & 0xFF,判断是否引用就 type & cJSON_IsReference,很干净。

举个例子,JSON 字符串 {"name": "Alice", "age": 25, "scores": [90, 95, 88]} 解析之后的内存结构差不多是这样:

  • 根节点 type 是 Object,child 指向第一个键值对 "name":"Alice"。
  • 该键值对节点:string="name",valuestring="Alice",type 是 String。它的 next 指向下一个键值对 "age":25,再 next 指向 "scores":[90,95,88]。
  • "scores" 的值是一个 Array 节点,它的 child 指向第一个数字节点 90,90 的 next 指向 95,再 next 指向 88。

数组/对象的一层就是一条双向链表(next/prev),向下走一层靠 child。另外有个小技巧:链表头的 prev 始终指向最后一个节点,这样在尾部追加节点就是 O(1)。

解析过程:从字符串到 cJSON 树

入口是 cJSON_Parse(const char *value),它内部调用 cJSON_ParseWithOpts,再调用 cJSON_ParseWithLengthOpts,初始化一个 parse_buffer,分配根节点,跳过可能的 BOM 和空白,然后调 parse_value 开始递归解析。如果要求必须 0 结尾,最后还会检查 \0。失败会走 goto fail 释放已分配节点,并设置 global_error。

所以核心就落在 parse_value 上,它根据当前字符决定下一步:

  • 见到 n 就匹配 null
  • f 匹配 false
  • t 匹配 true
  • " 调 parse_string 解析字符串,包括 \uXXXX 转义
  • - 或数字开头的调 parse_number,内部用 strtod,还做了 int 溢出饱和
  • [ 调 parse_array
  • { 调 parse_object
  • 其他字符直接失败

parse_array 和 parse_object 都会先检查深度是否超过 CJSON_NESTING_LIMIT(默认 1000),防止恶意嵌套撑爆栈。

parse_array 省略 [ 后,如果是 ] 就生成空数组;否则进 do-while 循环:每次 new 一个节点,挂到当前链表尾,然后递归 parse_value 解析这个元素,直到遇到 ] 或 , 继续。最后把 head->prev 指向尾节点,item->child = head。

parse_object 类似,只是每个循环里先 parse_string 得到键名(先临时存在 valuestring,然后移到 string 字段),跳过 :,再 parse_value 得到值,用 next/prev 串起来。

解析失败的时候,错误位置会记在 global_error,可以拿 cJSON_GetErrorPtr() 取到——不过多线程下这个变量是全局的,得小心。

生成:从 cJSON 树到字符串

生成由 cJSON_Print(带缩进和换行)或 cJSON_PrintUnformatted(紧凑)开始。内部 print 函数先分配一个 256 字节的 printbuffer,然后调 print_value 递归输出。输出过程中用 ensure 函数动态扩容,空间不够就两倍 realloc。

print_value 同样按 type 分派:Null 输出 "null",True/False 对应 "true"/"false",数字调用 print_number(NaN/Inf 输出 "null",整数用 %d,浮点数先用 15 位精度,不够再 17 位,并处理 locale 小数点),字符串调用 print_string_ptr 加引号并转义,Raw 类型直接输出 valuestring,数组和对象则分别输出 [...] 和 {...},中间递归调用 print_value。

返回的字符串需要用 cJSON_free 释放,否则内存泄漏。

回过头看,cJSON 有几个设计点我觉得挺巧的:

  • 树状链表:child 指向子节点,next/prev 串兄弟,加上 head->prev 指尾,既能表达复杂的 JSON 结构,又保证尾部插入 O(1)。
  • 位掩码类型:一个 int 既表示基本类型,又能标记是否引用、键名是否常量,省字段、好扩展。
  • 内存管理:解析失败统一 goto fail + cJSON_Delete,释放时对兄弟用循环、对子树用递归,并且尊重 IsReference 和 StringIsConst,避免错误释放。
  • 可插拔的分配器:用 internal_hooks 封装 malloc/free/realloc,用户能自己替换;如果不用标准库,就把 realloc 置空,内部会用 malloc+memcpy+free 来模拟扩容。
  • 数字处理:NaN/Inf 输出 "null",确保不出非法 JSON;小数解析时还会将 locale 下的其他小数点字符(比如逗号)替换回 .,兼顾本地化。
  • 嵌套深度限制:默认 1000 层,防止栈溢出,实际中很少有人会用到这么深的 JSON。

深度注释实践

如果像我一样要给 cJSON 加注释,可以分三层来写:

函数级:用 Doxygen 风格,说明作用、参数、返回值,尤其要写清楚谁负责释放内存、是否线程安全。

代码块注释:对一整段逻辑说明在做啥、为什么这么做、有哪些内存/错误处理的注意点。

关键行注释:对容易误解或者和安全相关的行做简短说明,比如 offset++ 跳过 [ 时注释一下,或者嵌套深度检查那里说明是为了防栈溢出。

在仓库的 docs/ 目录下,我已经放了两份带注释的副本 cJSON_annotated.c 和 cJSON_annotated.h,可以参考——注意它们不能直接替换原始文件用,只是文档。

如何运行与测试

说明:docs/cJSON_annotated.c 和 docs/cJSON_annotated.h 是带注释的文档型副本,不能直接替代工程里的 cJSON 使用;实际编译、运行、测试请用仓库根目录下的原始 cJSON.c 和 cJSON.h。

单文件 demo 编译(推荐)

在 fuzzing/docs/ 下已提供一个单文件测试程序 test_cjson_demo.c,用「原始 cJSON」编译即可:

Linux / macOS / MinGW(gcc):

cd /path/to/cJSON-1.7.19/fuzzing/docs
gcc -o test_cjson_demo test_cjson_demo.c ../../cJSON.c -I../../ -lm
./test_cjson_demo

Windows(MSVC Developer Command Prompt):

cd d:\path\to\cJSON-1.7.19\fuzzing\docs
cl test_cjson_demo.c ../../cJSON.c /I../../ /Fe:test_cjson_demo.exe
test_cjson_demo.exe

该 demo 覆盖:解析、打印、手动建树、类型判断、链表遍历、错误处理、Minify、Duplicate、版本信息等,可以和 cJSON_annotated.c/.h 的注释对照看。

用官方测试套件(CMake)

若要跑 cJSON 仓库自带的单元测试:

cd /path/to/cJSON-1.7.19
mkdir build && cd build
cmake ..
cmake --build .
ctest

以上大概就是 cJSON 1.7.19 的数据结构和解析/生成流程。真正用的时候,一个 demo 跑一遍,对照着看源码,很快就能上手。

参考资料

  • cJSON 源码:https://github.com/DaveGamble/cJSON
  • JSON 规范:https://www.json.org/
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 上手 YOLO12 WebUI:浏览器里跑目标检测,不用装环境
  • 在 M 系列 Mac 上安装 Vivado 的实用方法
  • Spring 国际化原理与实战:MessageSource、LocaleResolver、LocaleContextHolder、MessageFormat
  • OpenClaw 橙皮书与蓝皮书实战笔记
  • GESP 2024 年 3 月 C++ 二级判断题 1-10 解析
  • Flutter 使用 groq_sdk 实现鸿蒙端 AI 推理适配指南
  • 使用 Claude Code 辅助 Verilog 编程
  • 基于强化学习的多无人机对抗决策生成与优化方法研究
  • FPGA 摄像头采集处理显示指南:OV5640 至 HDMI 实时显示
  • 国产大语言模型翻译能力评测:十款主流模型对比分析
  • 少儿学习 Python 的重要性:升学考试与职业发展分析
  • llama.cpp 实战指南:在普通 CPU 上运行大模型
  • Claude Code + GLM4.7 修复前端 Bug 失败复盘:高 Token 消耗与工程化局限
  • AI 时代下生物细胞学的最新进展
  • OpenClaw 本地 AI 智能体入门与实战指南
  • Python dotenv 库 load_dotenv() 使用指南:环境变量管理与安全实践
  • 滑动窗口算法实战:串联所有单词的子串与最小覆盖子串
  • Linux 系统下 JDK 安装与环境配置实战
  • 渐进式 AIGC 系统:多模型集成与私有化部署方案
  • C++ 并发核心:内存序、可见性与指令重排

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online

目录

  1. cJSON 结构体
  2. 解析过程:从字符串到 cJSON 树
  3. 生成:从 cJSON 树到字符串
  4. 深度注释实践
  5. 如何运行与测试
  6. 单文件 demo 编译(推荐)
  7. 用官方测试套件(CMake)
  8. 参考资料
  • 免费图片AI生成工具免费生成了解详情