# zjson **Repository Path**: zhoutk/zjson ## Basic Information - **Project Name**: zjson - **Description**: 从node.js转到c++,特别怀念在js中使用json那种畅快感。在c++中也使用过了些库,但提供的接口使用方式,总不是习惯,很烦锁,接口函数太多,不直观。参考了很多库,如:rapidjson, cJson, CJsonObject, drleq-cppjson, json11等,受cJson的数据结构启发很大,决定用C++手撸一个。 - **Primary Language**: C++ - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 57 - **Forks**: 10 - **Created**: 2022-04-11 - **Last Updated**: 2026-09-17 ## Categories & Tags **Categories**: json-tools **Tags**: JSON, Cpp ## README # ZJSON    [English](README.md) [![JSONTestSuite](https://img.shields.io/badge/JSONTestSuite-283%2F283%20(100%25)-brightgreen)](docs/jsontestsuite_results.txt) [![C++17](https://img.shields.io/badge/C%2B%2B-17-blue)](https://isocpp.org/) [![header-only](https://img.shields.io/badge/header--only-yes-success)](src/zjson.hpp) [![license: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE) > 已对照 [`JSONTestSuite`](https://github.com/nst/JSONTestSuite) 全量 `test_parsing/` > 用例验证:严格模式下 **95/95** 个 `y_`(必须接受)与 **188/188** 个 `n_`(必须拒绝)全部通过。 > 详见 [`docs/jsontestsuite_results.txt`](docs/jsontestsuite_results.txt)。 > > 同时已将仓库内置的 [`JSON_checker`](thirds/JSON-c/README) 官方小套件纳入自动回归: > `thirds/JSON-c/test/` 下 **36/36** 个样例已通过,按其原始语义验收 > (顶层必须为 object/array,最大嵌套深度 19)。 近期补齐的 API 包括:`toString(indent)` pretty-print、语义比较 `==/!=`、支持结构化绑定的 `begin/end/cbegin/cend`、重复键 `ParseOptions`、JSON Pointer `at("/a/b/0")`、JSON Merge Patch / JSON Patch(`mergePatch(...)`、`applyPatch(..., err)`)、ADL `to_json` / `from_json` 类型映射钩子,以及内部 slab 节点池和解析字符串 arena 存储。 文档导航: - **[`docs/使用指南.md`](docs/使用指南.md)** —— 完整 API 语义、陷阱与速查卡(**接口以它为准**); - [`docs/从Qt迁移指南.md`](docs/从Qt迁移指南.md) —— 从 QJsonDocument/QJsonObject 迁移的差异清单; - [`docs/多线程使用指南.md`](docs/多线程使用指南.md) —— 线程契约、加锁是否够用、各模式的实测代价; - [`docs/性能测试报告.md`](docs/性能测试报告.md) —— 与 nlohmann / RapidJSON / simdjson 的对照数据。 ## 介绍 从node.js转到c++,特别怀念在js中使用json那种畅快感。在c++中也使用过了些库,但提供的接口使用方式,总不是习惯,很烦锁,接口函数太多,不直观。参考了很多库,如:rapidjson, cJson, CJsonObject, drleq-cppjson, json11等。数据结构受cJOSN启发很大,解析部分借鉴了json11,向他们致敬。最后因为数据存储需要不区分型别,又要能知道其型别,最终采用**类型标签 + union 的紧凑存储**(数字三态共用 8 字节;字符串在 owned / arena 借用视图之间切换),不继承、不用虚函数,C++版本定格在c++17,本库设计为单头文件,且不依赖c++标准库以外的任何库。 ## 项目名称说明 本人姓名拼音第一个字母z加上json,即得本项目名称zjson,没有其它任何意义。我将编写一系列以z开头的相关项目,命名是个很麻烦的事,因此采用了这种简单粗暴的方式。 ## 设计思路 简单的接口函数、简单的使用方法、灵活的数据结构、尽量支持链式操作。使用模板技术,得以完成最简设计,为Json对象增加子对象只需一个方法 ———— `add`,该方法自动识别是值对象还是子Json对象。采用链表结构(向cJSON致敬)来存储Json对象,请看我下面的数据结构设计,表头与后面的结点,都用使用一致的结构,这使得在索引操作([])时,可以进行链式操作。 ## 项目进度 项目目前完成大部分主要功能,具体情况请看任务列表。可以新建Json对象,增加数据,按key(Object类型)或索引(Array类型)提取相应的值或子对象,生成json字符串,并且实现从json字符串构造Json对象。 已经做过内存泄漏测试,析构函数能正确运行,百万级别生成与销毁未见内存明显增长。 编写了大量的单元测试用例,同时支持windws、linux和mac主流操作系统。 任务列表: - [x] 构造函数(Object & Array) - [x] 构造函数(值) - [x] JSON字符串反序列化构造函数 - [x] 复制构造函数 - [x] initializer_list构造函数 - [x] 析构函数 - [x] operator= - [x] operator[] - [x] contains - [x] getValueType - [x] take / takes (取值+删除;旧名 getAndRemove) - [x] getAllKeys - [x] add(为Json对象增加成员,为数组快速增加元素;旧名 addSubitem) - [x] toString(生成json字符串) - [x] toInt、toDouble、toBool 等值类型转换 - [x] toVector 数组类型转换 - [x] isError、isNull、isArray 等节点类型判断 - [x] parse, 从json字符串生成Json对象 - [x] Extend Json - 扩展对象 - [x] concat Json - 数组扩展 - [x] push_front - 数组压入队首 - [x] push_back - 数组压入队尾 - [x] insert - 数组插入 - [x] clear - 清空 - [x] std::move语义 - [x] Remove key - 删除所有键为key的数据(Json对象允许重复的key) - [x] Remove intger - 删除数组中指定序号元素 - [x] pop pop_back pop_front - [x] removeFirst removeLast remove(for array) - [x] slice - [x] takes take - [x] 性能测试与对照 harness - [x] 非递归算法 - [x] 节点池与解析字符串 arena 存储 - [x] 三态 Number(int64 / uint64 / double 精确存储) - [x] 线程安全:并发只读同一棵树 + 跨线程分配/释放(见 [`docs/多线程使用指南.md`](docs/多线程使用指南.md)) - [x] Qt 关键字宏共存(`slots`/`signals`/`foreach` 不再冲突,见 `tests/test_qt_macro_compat.cpp`) - [x] 直系子节点与安全变更辅助块(`directChild`/`hasChild`/`childValueOr`/`ownedKey`/`memberCount`/`isEmptyObject`/`setElement`/`setChild`) ## 数据结构 ### Json 节点类型定义 (内部使用,数据类型只在Json类内部使用) ``` enum Type { Error, //错误,查找无果,这是一个无效Json对象 False, //Json值类型 - false True, //Json值类型 - true Null, //Json值类型 - null Number, //Json值类型 - 数字,库中以三态存储:double / int64 / uint64 String, //Json值类型 - 字符串 Object, //Json类对象类型 - 这是Object嵌套,对象型中只有child需要关注 Array //Json类对象类型 - 这是Array嵌套,对象型中只有child需要关注 }; ``` ### Json 节点定义 ``` class Json { Json* brother; //兄弟/后继节点(与cJSON的next对应):对象成员或数组元素的下一个,名称仅在对象成员上有意义 Json* child; //第一个孩子节点,对象/数组类型才有效 Json* lastChild; //孩子链尾指针,使 append 为 O(1) atomic keymap; //对象的惰性键索引(const 读路径以 CAS 发布,见线程指南) Type type; //节点类型 NumberKind numberKind; //数字载荷当前生效的是哪一个成员(double / int64 / uint64) union { double; int64_t; uint64_t; } number; //节点数字数据(8 字节三态复用) StoredString valueString; //节点字符串数据(owned 或 arena 借用视图) StoredString name; //节点的key(对象成员的键名) } ``` > 说明:`valueString` / `name` 是内部的 `detail::StoredString`(带标签的联合:owned `std::string` > 或指向解析 arena 的视图),不是裸 `std::string`;这样每个字符串只在必要时才物化,`sizeof(Json)` 为 128 字节。 > 键名在能放进 `std::string` 内联缓冲时 owned、更长时借用 arena,两种情况下 `key()` 都不物化。 ## 接口说明 公开的对象类型,json只支持Object与Array两种对象,与内部类型对应(公开类型)。 ``` enum class JsonType { Object = 6, Array = 7 }; ``` 接口列表 - Json(JsonType type = JsonType::Object)      //默认构造函数,生成Object或Array类型的Json对象 - template<typename T> Json(const T& value)      //值构造函数(算术类型;另有 ADL `to_json` 重载) - Json(const float&) / Json(const double&) / Json(const bool&) / Json(const std::nullptr_t&) //字面量构造(`nullptr` 表示 null) - Json(const Json& origin)              //复制构造函数 - Json(Json&& rhs)              //移动构造函数 - Json(string jsonStr)               //反序列化构造函数 - explicit Json(std::initializer_list<std::pair<const std::string, Json>> values)     //initializer_list Object构造函数 - Json& operator = (const Json& origin)       //赋值操作 - Json& operator = (Json&& rhs)       //移动赋值操作 - Json operator[](const int& index)        //Json数组对象元素查询(**返回副本**) - Json operator[](const string& key)         //Json Object 对象按key查询(**返回副本**;直系未命中会**深搜**回退) - template<typename T> Json& add(T value)        //向Array追加元素(对Object无效) - template<typename T> Json& add(string name, T value)  //为Object增加成员;**追加**语义,同名键会留下重复成员(要替换语义用 `setChild`) - Json& add(const Json& value) / Json& add(Json&& value)  //同上,避免值对象重复拷贝 - [[nodiscard]] string toString() const         //Json对象序列化为字符串(紧凑单行) - [[nodiscard]] string toString(int indent) const     //美化输出(indent <= 0 等价于紧凑) - std::ostream& dumpTo(std::ostream& out, int indent = 0) const //直接写流,不先拼整串(大文档推荐) - std::ostream& dump(std::ostream& out, int indent = 0) const//写流(内部先拼整串);`operator<<` 等价于 dump - bool isError()                 //无效Json对象判定 - bool isNull()                  //null值判定 - bool isObject()                 //Object对象判定 - bool isArray()                 //Array对象判定 - bool isNumber()                 //number值判定,Json内使用三态存储number值 - bool isIntegral()                 //该number是否以int64/uint64存储(即无小数点的整数字面量) - bool isTrue()                  //true值判定 - bool isFalse()                 //false值判定 - int toInt()                    //值对象转为int - float toFloat()                  //值对象转为float - double toDouble()               //值对象转为double - int64_t toInt64()                //整数节点精确读取,不经double - uint64_t toUint64()               //整数节点精确读取,不经double - bool toBool()                  //值对象转为bool - vector<Json> toVector() const             //数组对象转为vector - Json& extend(Json value)            //对象扩展 - Json& concat(Json value)            //数组扩展 - Json& push_front(const Json& value)            //数组压入队首 - Json& push_back(const Json& value)            //数组压入队尾 - Json& insert(int index, const Json& value)           //数组插入元素 - Json& clear()             //清空 - Json& remove(const string &key, Json* self = nullptr, Json* prev = nullptr)          //删除键值 - bool contains(const string& key) const            //判断key是否存在 - string getValueType() const           //获取值类型字符串表示 - Json take(const string& key)           //获取并删除 - Json getAllKeys() const           //获取所有key 新增接口(2026-09-14) - const Json& atRef(string_view pointer)      //按 RFC 6901 指针取引用,不产生拷贝(失败返回错哨兵) - Json* findPtr(string_view key)            //取成员的可变指针(先直层,再深搜) - Json* findPtrAt(string_view pointer)        //按指针取可变指针(不存在返回 nullptr) - bool setAt(string pointer, const Json& value[, string& err]) //按指针写入(RFC 6902 "add" 语义;失败时文档不变) - template<typename T> bool try_get(const string& key, T& out) //不抛异常的取值,仅成功时写目标 - std::optional<int/double/string> try_int/try_double/try_string(const string& key) - static Json array(std::initializer_list<Json> values)    //构造数组,避免 `Json{...}` 歧义 - std::ostream& dumpTo(std::ostream& out, int indent = 0)   //直接写流,不先拼整串 - static Json ParseJson(std::string&& input, std::string& errMsg) //接管输入缓冲,不再复制文档文本 补充接口 - const Json* resolvePointerPtr(string_view pointer) const //按指针定位,nullptr = 不存在(零分配) - Json at(const string& pointer) const         //按指针取**副本**(失败返回 Error) - static Json ParseJsonStrict(input, err) / ParseJsonStrictUtf8(input, err) //严格模式 / 严格 + UTF-8 校验 - static Json FromFile(path)           //读文件(文档形状走移动解析;失败返回 Error) - Json& mergePatch(const Json& patch)       //RFC 7386 Merge Patch(就地生效) - Json applyPatch(const Json& operations, string& err) const  //RFC 6902 JSON Patch(返回新文档) - iterator / const_iterator,begin/end/cbegin/cend    //支持结构化绑定;`key()` 返回 `string_view` 直系访问与安全变更辅助(`zjson.hpp` 末尾,`namespace ZJSON`,2026-09-17 新增) 一组 `inline` 自由函数,把「只要直系成员」「不切断兄弟链」的语义集中化。**只依赖公开 API**,行为由 `tests/test_util.cpp` 全量锁定;使用场景与 ADL 注意事项见 [`docs/使用指南.md`](docs/使用指南.md) §8。 - const Json* directChild(const Json& object, string_view key) //只看直系(不深搜);缺失/非对象 → nullptr - bool hasChild(const Json& object, string_view key)   //直系成员是否存在 - Json childValueOr(const Json& object, string_view key, const Json& default) //直系取值,缺失给默认 - std::string ownedKey(string_view key)        //物化迭代器 `key()`(string_view → std::string) - int memberCount(const Json& object)        //对象成员数(非对象返回 0) - bool isEmptyObject(const Json& object)      //对象判空(唯一正确方式,勿用 `isEmpty()`) - bool setElement(Json& array, int index, const Json& value) //数组元素替换(兄弟链存活) - void setChild(Json& object, string_view key, const Json& value) //直系成员替换/新增(去重,保持成员顺序) > ⚠ 三个必记的坑(也是这组函数存在的原因):`operator[]` 返回**副本**(`obj["k"] = v` 赋值无效); > `operator=` 会切断 `brother` 链(`it.value() = v` 会丢掉后面的所有元素);`size()` / `isEmpty()` > 对 Object 分别是 **-1** / **恒 true**。 > > ⚠ `directChild` 返回的是**指向文档内部的指针**,多线程下不要让它活过临界区(改用值语义的 `childValueOr`), > 详见 [`docs/多线程使用指南.md`](docs/多线程使用指南.md) §3.1。 需要知道的语义 - `operator[]` 返回**副本**(容器成员会深拷贝子树);需要就地读写请用 `findPtr`/`findPtrAt`/`atRef`。 - 键不是直层成员时的深搜回退返回**文档序第一个匹配**(先序)。 - 解析限深 **101 层**;`cloneChain`/`deleteJson`/美化打印/比较均已迭代化,因此通过 API 自建的 20000 层文档可以安全拷贝、打印、比较与销毁。 - 相等性把成员当**多重集**:重复键必须数量与取值配对一致(解析本身会合并重复键,默认保留最后一个)。 - **`key()` 返回 `std::string_view`(2026-09-16 起的破坏性变更)**:`entry.key()`、`it.key()`、`it->key()` 都不再返回 `const string&`。`std::string` 从 `string_view` 的转换构造是 **explicit**,因此**只有「拷贝初始化」场景**会编译失败——声明处的 `=`、按值传参、`return` 到 `std::string`、`push_back`: | 写法 | 结果 | |---|---| | `std::string k = e.key();`(拷贝初始化) | ❌ 编译失败 | | `take(e.key())`(形参按值)= `return e.key();` = `v.push_back(e.key())` | ❌ 编译失败 | | `std::string k(e.key());`(直接初始化) | ✅ | | `std::string k; k = e.key();`(赋值,不是初始化) | ✅ | | `s += e.key();` / `s.append(e.key());` / `v.emplace_back(e.key())` | ✅ | | 比较、`.empty()`/`.size()`、结构化绑定、`std::string_view` 直接使用 | ✅ | 注意 `std::string k = e.key();`(声明,失败)与 `k = e.key();`(赋值,成功)行为不同。迁移方式:加个括号 `std::string(e.key())`,或改用 `std::string_view`,或用辅助块的 `ZJSON::ownedKey(e.key())`(可读性最好,函数名即语义)。 - `key()`/`it.key()` 返回的视图只在「文档存活且该成员未被改名」期间有效(长键时它指向解析 arena);需要留存请**在锁内/使用期内**转成 `std::string`。 - 对象**键名**在能放进 `std::string` 内联缓冲时 owned、更长时借用解析 arena(保证不分配,也不存在任何会就地改写节点的读路径);字符串**值**始终借用 arena。 - 结构化绑定依赖 ADL `get` + `std::tuple_size`/`std::tuple_element`;**刻意不提供** `std::get(entry)`(为自己的类型向 `namespace std` 加重载是 UB)。 - **整数字面量**(无小数点、无指数)按 `int64`/`uint64` 精确存储并原样写回,`{"id":9007199254740993}` 往返不变;`42.0`、`42e0` 与 `-0` 仍按 `double` 处理(用 `isIntegral()` 区分)。第三个数值状态**不增加节点内存**:kind 标记落在 `type` 之后原有的填充位。 - 性能,以及与 nlohmann/json、RapidJSON、simdjson 的对比(吞吐、节点池开销、取值路径开销、stringify 热点):见 [`docs/性能测试报告.md`](docs/性能测试报告.md),原始中位数数据在 `docs/benchmark_2026-09-15_clang64_medians.csv`。 - 2026-09-15 两轮性能改动:宽对象键索引(parse 几何平均 **+23%**,100 KB 扁平文档 **1.75×**)与 R2 节点瘦身(`sizeof(Json)` 176→128、copy **+16.6%**、节点 churn **−12.7%**)——A/B 证据、方案可行性实测与被否决的候选:见 [`docs/性能优化实施与评估-2026-09-15.md`](docs/性能优化实施与评估-2026-09-15.md)。R2 的独立复核:[`docs/复核-2026-09-15-R2与UAF归因.md`](docs/复核-2026-09-15-R2与UAF归因.md)。 ## 线程与内存契约 1. **节点分配/释放线程安全**:每个线程拥有自己的 slab 池且永不释放,所以「A 线程分配、B 线程释放」(甚至 A 线程已退出)都安全,且无锁。 2. **同一棵树的并发只读是安全的**:惰性 key 索引由 const 读路径以原子 CAS 发布(多线程竞争时共用一张已建好的表),而且**没有任何 const 读路径会写节点** —— `entry.key()`/`it.key()` 返回 `std::string_view`,不物化、不分配、不可能抛异常。**并发读写同一棵树仍不安全**(与 `std::string` 相同)。外部加锁只有在「所有读都走值语义 API、且没有任何内部引用/指针活过临界区」时才有效;`atRef`/`findPtr`/迭代器/`key()` 的返回值都属于后者。完整用法、反模式与各模式的实测代价见 [`docs/多线程使用指南.md`](docs/多线程使用指南.md)。 3. **池驻留按线程计算且设计上不回收**:每个线程保留其用过的 slab(实测:64 个短命线程各自解析 4 万节点,进程永久增长 ≈440MB)。请复用工作线程,不要「每请求一线程」。 4. **跨模块所有权不保证安全**:header 会被内联进每个模块,跨 DLL 传递并在模块卸载后析构的文档不安全。 5. 深文档的拷贝/打印/比较/销毁是安全的(迭代化),但解析仍然拒绝超过 101 层的嵌套。 多线程的**完整用法指南**(哪些场景本来就安全、自己加锁够不够、四种模式的实测价格、反模式清单): [`docs/多线程使用指南.md`](docs/多线程使用指南.md)。两个只读竞态的定位与修复过程: [`docs/线程安全审查与修复-2026-09-16.md`](docs/线程安全审查与修复-2026-09-16.md)。 ## 编程示例 简单使用示例 ``` Json subObject{{"math", 99},{"str", "a string."}}; //initializer_list方式构造Json对象 //initializer_list方式构造Json对象, 并且可以嵌套 Json mulitListObj{{"fkey", false},{"strkey","ffffff"},{"num2", 9.98}, {"okey", subObject}}; Json subArray(JsonType::Array); //数组对象以initializer_list方式增加元素 subArray.add({12,13,14,15}); //快速生成 [12,13,14,15] array json Json ajson(JsonType::Object); //新建Object对象,输入参数可以省略 std::string data = "kevin"; ajson.add("fail", false); //增加false值对象 ajson.add("name", data); //增加字符串值对象 ajson.add("school-en", "the 85th."); ajson.add("age", 10); //增加number值对象,此处为整数 ajson.add("scores", 95.98); //增加number值对象,此处为浮点数,还支持long,long long ajson.add("nullkey", nullptr); //增加null值对象,需要送入nullptr, NULL会被认为是整数0 Json sub; //新建Object对象 sub.add("math", 99); ajson.addValueJson("subJson", sub); //为ajson增加子Json类型对象,完成嵌套需要 Json subArray(JsonType::Array); //新建Array对象,输入参数不可省略 subArray.add("I'm the first one."); //增加Array对象的字符串值子对象 subArray.add("two", 2); //增加Array对象的number值子对象,第一个参数会被忽略 Json sub2; sub2.add("sb2", 222); subArray.addValueJson("subObj", sub2); //为Array对象增加Object类子对象,完成嵌套需求 ajson.addValueJson("array", subArray); //为ajson增加Array对象,且这个Array对象本身就是一个嵌套结构 std::cout << "ajson's string is : " << ajson.toString() << std::endl; //输出ajson对象序列化后的字符串, 结果见下方 string name = ajson["name"].toString(); //提取key为name的字符串值,结果为:kevin int oper = ajson["sb2"].toInt(); //提取嵌套深层结构中的key为sb2的整数值,结果为:222 Json operArr = ajson["array"]; //提取key为array的数组对象 string first = ajson["array"][0].toString(); //提取key为array的数组对象的序号为0的值,结果为:I'm the first one. ``` mulitListObj序列化后结果为: ``` { "fkey": false, "strkey": "ffffff", "num2": 9.98, "okey": { "math": 99, "str": "a string." } } ``` ajson序列化后结果为: ``` { "fail": false, "name": "kevin", "school-en": "the 85th.", "age": 10, "scores": 95.98, "nullkey": null, "subJson": { "math": 99 }, "array": [ "I'm the first one.", 2, { "sb2": 222 } ] } ``` 详情请参看demo.cpp或tests目录下的测试用例 ## 项目地址 ``` https://gitee.com/zhoutk/zjson 或 https://github.com/zhoutk/zjson ``` ## 运行方法 该项目在vs2019, gcc7.5, clang12.0下均编译运行正常。 ``` git clone https://github.com/zhoutk/zjson cd zjson cmake -Bbuild . ---windows cd build && cmake --build . ---linux & mac cd build && make run run ctest --test-dir out/build/x64-release --output-on-failure ``` ## 相关项目 会有一系列项目出炉,网络服务相关,敬请期待... > [zorm](https://gitee.com/zhoutk/zorm.git) (关系数据库的通用封装) ``` https://gitee.com/zhoutk/zorm 或 https://github.com/zhoutk/zorm ```