Ingen beskrivning

RyanCW cbda13f753 test: 优化部分测试代码 7 timmar sedan
.vscode cbda13f753 test: 优化部分测试代码 7 timmar sedan
RyanJson b8b98ca7a2 refactor: 优化double打印实现 17 timmar sedan
example cbda13f753 test: 优化部分测试代码 7 timmar sedan
test cbda13f753 test: 优化部分测试代码 7 timmar sedan
.clang-format f5f0ad5a3c refactor: 持续优化 2 veckor sedan
.clang-format-ignore c02b973431 test: 优化测试代码 4 dagar sedan
.clang-tidy 9c9d896ce4 refactor: 优化json存储结构 6 dagar sedan
.clangd 9c9d896ce4 refactor: 优化json存储结构 6 dagar sedan
.gitignore cbda13f753 test: 优化部分测试代码 7 timmar sedan
LICENSE 61ec1f4bf1 Initial commit 2 år sedan
Makefile c02b973431 test: 优化测试代码 4 dagar sedan
README.md 0cb3edc307 doc: 更新readme 3 dagar sedan
SConscript ea78d25249 chore(scon): scon依赖变更,适配RT-Thread软件包 1 år sedan
run_base_coverage.sh cbda13f753 test: 优化部分测试代码 7 timmar sedan
run_coverage.sh cbda13f753 test: 优化部分测试代码 7 timmar sedan
xmake.lua cbda13f753 test: 优化部分测试代码 7 timmar sedan

README.md

RyanJson

希望有兴趣的大佬多试试,找找bug、提提意见

📢 使用过程中遇到问题?欢迎提交 Issue 或在 RT-Thread 社区 提问,感谢支持!

一个针对资源受限的嵌入式设备优化的Json库,内存占用极小的通用Json库,简洁高效!

示例代码请参考example文件夹!

1、介绍

RyanJson 是一个针对嵌入式平台深度优化的 C 语言 JSON 解析器。它在保持代码健壮性的同时,实现了极致的内存控制,旨在解决 cJSON 等传统库在复杂场景下内存占用过高的问题。

初衷:项目重构后 JSON 结构复杂度提升,cJSON 内存占用过高,无法满足嵌入式场景需求。

✅ 特性亮点

  • 💡 极致内存优化:通过动态内存扩展与紧凑结构设计,相比 cJSON 减少 50% 左右的内存占用,且运行速度基本持平。
  • 🔍 模糊测试保障:基于LLVM Fuzzer 生成上亿级测试用例,分支覆盖率 100%,确保在各种非法输入和极端场景下依旧安全。点击在线查看覆盖率信息
  • 🧪 9 大类专项测试用例:覆盖广泛场景,全链路内存泄漏检测,强化稳定性与可靠性
  • 🛡️ 运行时安全分析验证:使用 Sanitizer 系列工具,捕获内存越界、Use-after-free、数据竞争、未定义行为、内存泄漏等问题,提升代码健壮性与安全性
  • 📐高质量代码保障: 引入 clang-tidyCppcheck 进行静态分析,代码质量接近语法级的"零缺陷"
  • 🤖 AI 辅助开发与审查:结合 Gemini Code AssistcoderabbitaiCopilot ,在编码与代码审查阶段持续优化代码质量,构建多层安全防线
  • 👩‍💻 开发者友好:类 cJSON 接口设计,迁移成本低
  • 📜 严格但不严苛:符合 RFC 8259 绝大部分标准,支持无限嵌套(受限于栈空间),支持注释与尾随逗号(可配置)

2、设计

**RyanJson设计时借鉴了 jsoncJSON ! **

Json语法是JavaScript对象语法的子集,可通过下面两个连接学习json语法。

JSON规范

Parsing JSON is a Minefield 建议看看

RyanJson 的核心在于对内存布局的精细控制,结构体表示最小存储单元(键值对),通过单链表组织数据,结构如下:

// Json 的最基础节点,所有 Json 元素都由该节点表示。
// 结构体中仅包含固定的 next 指针,用于单向链表串联。
// 其余数据(flag、key、stringValue、numberValue、doubleValue 等)均通过动态内存分配管理。
struct RyanJsonNode
{
	struct RyanJsonNode *next; // 单链表节点指针

	/**
	 * @brief RyanJson 节点结构体
	 * 每个节点由链表连接,包含元数据标识 (Flag) 与动态载荷存储区。
	 *
	 * 内存布局:
	 * [ next指针 | flag(1字节) | padding/指针空间 | 动态载荷区 ]
	 *
	 * @brief 节点元数据标识 (Flag)
	 * 紧跟 next 指针后,利用 1 字节位域描述节点类型及存储状态。
	 *
	 * flag 位分布定义:
	 * bit7   bit6   bit5   bit4   bit3   bit2   bit1   bit0
	 * -----------------------------------------------------
	 * strMode KeyLen KeyLen HasKey NumExt Type2  Type1  Type0
	 *
	 * 各位含义:
	 * - bit0-2 : 节点类型
	 *            000=Unknown, 001=Null, 010=Bool, 011=Number,
	 *            100=String, 101=Array, 110=Object, 111=Reserved
	 *
	 * - bit3   : 扩展位
	 *            Bool 类型:0=false, 1=true
	 *            Number 类型:0=int(4字节), 1=double(8字节)
	 *
	 * - bit4   : 是否包含 Key
	 *            0=无 Key(数组元素)
	 *            1=有 Key(对象成员)
	 *
	 * - bit5-6 : Key 长度字段字节数
	 *            00=1字节 (≤UINT8_MAX)
	 *            01=2字节 (≤UINT16_MAX)
	 *            10=3字节 (≤UINT24_MAX)
	 *            11=4字节 (≤UINT32_MAX)
	 *
	 * - bit7   : 表示key / strValue 存储模式
	 *            0:inline 模式, 1=ptr 模式
	 *
	 * @brief 动态载荷存储区
     * 目的:
     * - 在保持 API 易用性和稳定性的同时,最大限度减少 malloc 调用次数。
     * - 尤其在嵌入式平台,malloc 代价高昂:不仅有堆头部空间浪费,还会产生内存碎片。
     * - 通过利用结构体内的对齐填充 (Padding) 和指针空间,形成一个灵活的缓冲区。
     *
     * 存储策略:
     * 利用结构体内存对齐产生的 Padding(如 Flag 后的空隙)以及原本用于存储指针的空间,形成一个缓冲区
     * 若节点包含 key / strValue,则可能有两种方案:
     * 1. inline 模式 (小数据优化)
     *    - 当 (KeyLen + Key + Value) 的总长度 ≤ 阈值时,直接存储在结构体内部。
     *    - 阈值计算公式:
     *        阈值 = Padding + sizeof(void*) + (malloc头部空间的一半),再向上对齐到字节边界。
     *      举例:
     *        - 内存对齐:4字节
     *        - malloc头部空间:8字节
     *        - 可用空间 = 3 (flag后padding) + 4 (指针空间) + 4 (malloc头部一半)
     *        - 向上对齐后得到阈值12字节
     *    - 存储布局:
     *        [ KeyLen | Key | Value ]
     *      起始地址即为 flag 之后,数据紧凑排列,无需额外 malloc。
     *
     * 2. ptr 模式 (大数据)
     *    - 当数据长度 > 阈值时,结构体存储一个指针,指向独立的堆区。
     *    - 存储布局:
     *        [ KeyLen | *ptr ] -> (ptr指向) [ Key | Value ]
     *    - KeyLen 的大小由 flag 中的长度字段决定 (最多 4 字节)。
     *    - 这样保证大数据不会撑爆结构体,同时保持 API 一致性。

     * 其他类型的存储:
	 * - null / bool : 由 flag 位直接表示,无需额外空间。
	 * - number      : 根据 flag 扩展位决定存储 int(4字节) 或 double(8字节)。
	 * - object      : 动态分配空间存储子节点,采用链表结构。
     *
     * 设计考量:
     * - malloc 在嵌入式平台的开销:
     *    * RTT 最小内存管理算法中,malloc 头部约 12 字节(可以考虑tlsf算法头部空间仅4字节,内存碎片也控制的很好,适合物联网应用)。
     *    * 一个 RyanJson 节点本身可能只有个位数字节,头部空间就让内存占用翻倍。
     * - 因此:
     *    * 小数据尽量 inline 存储,避免二次 malloc。
     *    * 大数据 fallback 到 ptr 模式,保证灵活性。
     * - 修改场景:
     *    * 理想情况:节点结构体后面直接跟 key/strValue,修改时释放并重新申请节点。
     *    * 但这样 changKey/changStrValue 接口改动太大,用户层需要修改指针,代价高。
     *    * 实际策略:提供就地修改接口。
     *        - 若新值长度 ≤ 原有 inline 缓冲区,直接覆盖。
     *        - 若超过阈值,自动切换到 ptr 模式,用户层无需关心。
	 *
     * 链表结构示例:
     *   {
     *       "name": "RyanJson",
     *   next (
     *       "version": "xxx",
     *   next (
     *       "repository": "https://github.com/Ryan-CW-Code/RyanJson",
     *   next (
     *       "keywords": [
     *           "json",
     *       next (
     *           "streamlined",
     *       next (
     *           "parser"
     *       ))
     *       ],
     *   next (
     *       "others": { ... }
     *   }
	 */
};

typedef struct RyanJsonNode *RyanJson_t;

3、测试与质量保障

🧪 专项基础功能测试

测试类别 测试目标
修改 Json 节点测试 验证字段动态更新及存储模式(Inline/Ptr)的自动切换逻辑
比较 Json 节点测试 验证节点及其属性的递归深度一致性比较与逻辑等价性
创建 Json 节点树测试 验证全类型节点的构造初始化及深层嵌套结构的正确性
删除 Json 节点测试 验证节点及其子项的递归内存回收机制,防止内存泄漏
分离 Json 节点测试 验证节点的分离操作、所属权转移及引用关系的生命周期管理
复制 Json 节点测试 验证对象深拷贝 (Deep Copy) 的数据完整性与拓扑结构一致性
Json 循环测试 验证数组/对象迭代器在不同数据规模下的稳定性与边界行为
文本解析 Json 测试 验证复杂 JSON 文本解析的健壮性及内存映射的准确性
替换 Json 节点测试 验证成员节点就地替换时的内存复用策略与逻辑一致性

🔍LLVM Fuzzer 模糊测试(核心亮点)

LLVM Fuzzing 模糊测试是 RyanJson 的 核心稳定性保障

点击在线查看覆盖率信息

  • 上亿级测试样本LLVM Fuzzer 自动生成并执行上亿级随机与非法 JSON 输入
  • 覆盖率极高:分支覆盖率 100%(希望以后也能保持),无崩溃、无泄漏
  • 鲁棒性验证:内存申请失败、扩容失败、非法转义字符、尾随逗号、嵌套过深、随机类型切换
  • 内存安全验证:结合 Sanitizer 工具链,确保无泄漏、无悬空指针、无越界
测试类别 测试目标
内存故障模拟测试 验证在堆内存耗尽 (OOM) 场景下的异常回滚及系统稳定性
随机解析鲁棒性测试 验证对非法语法、畸形字符及极端输入的容错与边界防御能力
循环遍历删除安全性测试 验证链表迭代过程中动态删除节点的双向一致性与指针安全性
循环遍历分离安全性测试 验证在高频率迭代中分离节点后的拓扑重构与内存权属逻辑
转义序列极致压缩测试 验证复杂及异常转义字符在高效压缩过程中的解析完整性
序列化回环一致性测试 验证 JSON 对象经过“解析-打印-解析”链路后数据的不失真性
高频迭代值读取测试 验证在不同层级结构下随机访问 Value 字段的寻址效率与稳定性
随机压力复制安全测试 验证大规模深拷贝过程中内存池的利用效率与拓扑结构安全性
动态类型切换压力测试 验证节点在运行期进行类型强制转换与动态扩展时的内存安全性

🛡️ 工具链全面集成

工具 用途
Sanitizer 运行时检测,捕获内存泄漏、越界、数据竞争。杜绝泄漏、越界、悬空指针
clang-tidy 静态分析潜在缺陷(空指针、资源泄漏等)
Cppcheck 深度扫描内存与资源问题
ClangFormat 统一代码风格
AI 审查 Gemini Code AssistcoderabbitaiCopilot 辅助优化逻辑,构建多层安全防线
编译器警告 -Wall -Wextra(默认)、-Weffc++/-Weverything(Clang 可选,CI 强化时开启)

4、基准测试

测试代码和示例代码可在本项目根目录 testRyanJsonExample 文件夹查看。

性能测试

请移步文末的 RyanDocs 文档中心查看,是基于 yyjson_benchmark 的测试结果。(已经过时,仅供参考)

内存占用测试

(20251222 malloc头部空间4字节,内存对齐4字节)测试代码可在本项目根目录RyanJsonExample文件夹查看。

*****************************************************************************
*************************** RyanJson / cJSON / yyjson 内存对比程序 **************************
*****************************************************************************
┌── [TEST 1 | test/RyanJsonMemoryFootprintTest.c:294] 开始执行: testMixedJsonMemory()
json原始文本长度为 2265, 序列化后RyanJson内存占用: 4912, cJSON内存占用: 11336, yyjson内存占用: 8784
比cJSON节省: 56.67% 内存占用, 比yyjson节省: 44.08% 内存占用
└── [TEST 1 | test/RyanJsonMemoryFootprintTest.c:294] 结束执行: 结果 ✅ | 耗时: 0 ms

┌── [TEST 2 | test/RyanJsonMemoryFootprintTest.c:295] 开始执行: testObjectJsonMemory()
json原始文本长度为 3991, 序列化后RyanJson内存占用: 7944, cJSON内存占用: 16020, yyjson内存占用: 12884
比cJSON节省: 50.41% 内存占用, 比yyjson节省: 38.34% 内存占用
└── [TEST 2 | test/RyanJsonMemoryFootprintTest.c:295] 结束执行: 结果 ✅ | 耗时: 1 ms

┌── [TEST 3 | test/RyanJsonMemoryFootprintTest.c:296] 开始执行: testArrayJsonMemory()
json原始文本长度为 1205, 序列化后RyanJson内存占用: 3696, cJSON内存占用: 8680, yyjson内存占用: 5068
比cJSON节省: 57.42% 内存占用, 比yyjson节省: 27.07% 内存占用
└── [TEST 3 | test/RyanJsonMemoryFootprintTest.c:296] 结束执行: 结果 ✅ | 耗时: 0 ms

┌── [TEST 4 | test/RyanJsonMemoryFootprintTest.c:297] 开始执行: testSmallMixedJsonMemory()
json原始文本长度为 90, 序列化后RyanJson内存占用: 168, cJSON内存占用: 392, yyjson内存占用: 648
比cJSON节省: 57.14% 内存占用, 比yyjson节省: 74.07% 内存占用
└── [TEST 4 | test/RyanJsonMemoryFootprintTest.c:297] 结束执行: 结果 ✅ | 耗时: 0 ms

┌── [TEST 5 | test/RyanJsonMemoryFootprintTest.c:298] 开始执行: testSmallStringJsonMemory()
json原始文本长度为 100, 序列化后RyanJson内存占用: 216, cJSON内存占用: 472, yyjson内存占用: 648
比cJSON节省: 54.24% 内存占用, 比yyjson节省: 66.67% 内存占用
└── [TEST 5 | test/RyanJsonMemoryFootprintTest.c:298] 结束执行: 结果 ✅ | 耗时: 0 ms

┌── [TEST 6 | test/RyanJsonMemoryFootprintTest.c:299] 开始执行: testCompressedBusinessJsonMemory()
json原始文本长度为 551, 序列化后RyanJson内存占用: 1184, cJSON内存占用: 3788, yyjson内存占用: 3020
比cJSON节省: 68.74% 内存占用, 比yyjson节省: 60.79% 内存占用
└── [TEST 6 | test/RyanJsonMemoryFootprintTest.c:299] 结束执行: 结果 ✅ | 耗时: 0 ms

RT-Thread平台使用最小内存算法默认 malloc头部空间12字节,内存对齐8字节测试代码可在本项目根目录RyanJsonExample文件夹查看

*****************************************************************************
*************************** RyanJson / cJSON / yyjson 内存对比程序 **************************
*****************************************************************************
┌── [TEST 1 | test/RyanJsonMemoryFootprintTest.c:294] 开始执行: testMixedJsonMemory()
json原始文本长度为 2265, 序列化后RyanJson内存占用: 7292, cJSON内存占用: 14948, yyjson内存占用: 8852
比cJSON节省: 51.22% 内存占用, 比yyjson节省: 17.62% 内存占用
└── [TEST 1 | test/RyanJsonMemoryFootprintTest.c:294] 结束执行: 结果 ✅ | 耗时: 0 ms

┌── [TEST 2 | test/RyanJsonMemoryFootprintTest.c:295] 开始执行: testObjectJsonMemory()
json原始文本长度为 3991, 序列化后RyanJson内存占用: 11308, cJSON内存占用: 21068, yyjson内存占用: 13016
比cJSON节省: 46.33% 内存占用, 比yyjson节省: 13.12% 内存占用
└── [TEST 2 | test/RyanJsonMemoryFootprintTest.c:295] 结束执行: 结果 ✅ | 耗时: 0 ms

┌── [TEST 3 | test/RyanJsonMemoryFootprintTest.c:296] 开始执行: testArrayJsonMemory()
json原始文本长度为 1205, 序列化后RyanJson内存占用: 5644, cJSON内存占用: 11284, yyjson内存占用: 5104
比cJSON节省: 49.98% 内存占用, 比yyjson节省: -10.58% 内存占用
└── [TEST 3 | test/RyanJsonMemoryFootprintTest.c:296] 结束执行: 结果 ✅ | 耗时: 0 ms

┌── [TEST 4 | test/RyanJsonMemoryFootprintTest.c:297] 开始执行: testSmallMixedJsonMemory()
json原始文本长度为 90, 序列化后RyanJson内存占用: 252, cJSON内存占用: 520, yyjson内存占用: 676
比cJSON节省: 51.54% 内存占用, 比yyjson节省: 62.72% 内存占用
└── [TEST 4 | test/RyanJsonMemoryFootprintTest.c:297] 结束执行: 结果 ✅ | 耗时: 0 ms

┌── [TEST 5 | test/RyanJsonMemoryFootprintTest.c:298] 开始执行: testSmallStringJsonMemory()
json原始文本长度为 100, 序列化后RyanJson内存占用: 272, cJSON内存占用: 620, yyjson内存占用: 676
比cJSON节省: 56.13% 内存占用, 比yyjson节省: 59.76% 内存占用
└── [TEST 5 | test/RyanJsonMemoryFootprintTest.c:298] 结束执行: 结果 ✅ | 耗时: 0 ms

┌── [TEST 6 | test/RyanJsonMemoryFootprintTest.c:299] 开始执行: testCompressedBusinessJsonMemory()
json原始文本长度为 551, 序列化后RyanJson内存占用: 2032, cJSON内存占用: 4872, yyjson内存占用: 3056
比cJSON节省: 58.29% 内存占用, 比yyjson节省: 33.51% 内存占用
└── [TEST 6 | test/RyanJsonMemoryFootprintTest.c:299] 结束执行: 结果 ✅ | 耗时: 0 ms

RFC 8259 标准符合性测试

RyanJson使用Double存储浮点数,超大数字会丢失精度

如果项目需要完全兼容Unicode字符集,可以考虑yyjson / json-c

*****************************************************************************
*************************** RyanJson / cJSON / yyjson RFC8259标准测试 **************************
*****************************************************************************
┌── [TEST 1 | test/RFC8259Test/RyanJsonRFC8259JsonTest.c:282] 开始执行: testRFC8259RyanJson()
1 数据不完全一致 -- 原始: [-1e+9999] -- 序列化: [null]
2 数据不完全一致 -- 原始: [123123e100000] -- 序列化: [null]
3 数据不完全一致 -- 原始: [-123123e100000] -- 序列化: [null]
4 数据不完全一致 -- 原始: [0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006] -- 序列化: [null]
5 数据不完全一致 -- 原始: [1.5e+9999] -- 序列化: [null]
RFC 8259 JSON: (322/322)
└── [TEST 1 | test/RFC8259Test/RyanJsonRFC8259JsonTest.c:282] 结束执行: 结果 ✅ | 耗时: 69 ms

┌── [TEST 2 | test/RFC8259Test/RyanJsonRFC8259JsonTest.c:283] 开始执行: testRFC8259yyjson()
RFC 8259 JSON: (322/322)
└── [TEST 2 | test/RFC8259Test/RyanJsonRFC8259JsonTest.c:283] 结束执行: 结果 ✅ | 耗时: 8 ms

┌── [TEST 3 | test/RFC8259Test/RyanJsonRFC8259JsonTest.c:284] 开始执行: testRFC8259cJSON()
应该失败,但是成功: [012], len: 5
应该失败,但是成功: [2.e+3], len: 7
1 数据不完全一致 -- 原始: [0e1] -- 序列化: [0]
应该失败,但是成功: 123, len: 4
应该失败,但是成功: [0.e1], len: 6
2 数据不完全一致 -- 原始: [-1e+9999] -- 序列化: [null]
3 数据不完全一致 -- 原始: [123123e100000] -- 序列化: [null]
4 数据不完全一致 -- 原始: [ -- 序列化: [""]
应该失败,但是成功: ["new
line"], len: 12
5 数据不完全一致 -- 原始: [-123123e100000] -- 序列化: [null]
6 数据不完全一致 -- 原始: [1E+2] -- 序列化: [100]
应该失败,但是成功: [1.], len: 4
7 数据不完全一致 -- 原始: [0e+1] -- 序列化: [0]
8 数据不完全一致 -- 原始: ["a -- 序列化: ["a"]
应该失败,但是成功: ["a, len: 7
应该失败,但是成功: [-012], len: 6
9 数据不完全一致 -- 原始: [0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006] -- 序列化: [null]
10 数据不完全一致 -- 原始: ["\uqqqq"] -- 序列化: [""]
应该失败,但是成功: ["\uqqqq"], len: 10
11 数据不完全一致 -- 原始: [
                            ] -- 序列化: []
应该失败,但是成功: [
                     ], len: 3
12 数据不完全一致 -- 原始: [ -- 序列化: []
应该失败,但是成功: [, len: 3
13 数据不完全一致 -- 原始: [1.5e+9999] -- 序列化: [null]
14 数据不完全一致 -- 原始:  -- 序列化: [""]
应该失败,但是成功: [2.e-3], len: 7
应该失败,但是成功: [2.e3], len: 6
应该失败,但是成功: ["  "], len: 5
15 数据不完全一致 -- 原始: [1e+2] -- 序列化: [100]
应该失败,但是成功: [-01], len: 5
应该失败,但是成功: [-.123], len: 7
16 数据不完全一致 -- 原始: {} -- 序列化: {}
17 数据不完全一致 -- 原始: [20e1] -- 序列化: [200]
18 数据不完全一致 -- 原始: [123.456e-789] -- 序列化: [0]
19 数据不完全一致 -- 原始: [123e-10000000] -- 序列化: [0]
应该失败,但是成功: [-2.], len: 5
RFC 8259 JSON: (305/322)
└── [TEST 3 | test/RFC8259Test/RyanJsonRFC8259JsonTest.c:284] 结束执行: 结果 ✅ | 耗时: 7 ms

5、局限性与注意事项

  • 数值精度:内部使用 int / double 存储 Number。对于超过 double 精度的 64 位整数或高精度浮点数,建议作为 String 处理以避免精度丢失。
  • 重复 Key:RyanJson 允许对象中存在重复 Key(解析时不报错),但在查找时只会返回链表中第一个匹配项。

6、文档

📂 示例代码:RyanJsonExample 文件夹

📖 文档中心:RyanDocs

📧 联系与支持:如有任何疑问或商业合作需求,请联系:1831931681@qq.com