资讯动态

JSON for Modern C++(nlohmann/json)值修改完全指南:push_back、emplace、update 与 erase 实战

发布时间:2026/9/9 22:10:36 来源:尧图企业网站定制
JSON for Modern Cnlohmann/json值修改完全指南push_back、emplace、update 与 erase 实战【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json创建 JSON 值只是第一步实际开发中绝大多数工作是“修改”向数组追加元素、向对象插入或替换成员、将两个对象合并、按需删除或清空数据。本文以 nlohmann/jsonJSON for Modern C官方文档 modifying_values 为主线系统讲解对已存在 JSON 值进行增、改、并、删的全部核心 APIpush_back/emplace_back、operator[]/emplace、update、erase/clear。读完本文你将掌握每个修改接口的调用方式、null值的隐式类型转换规则、复杂度与异常语义以及递归合并、按“不存在才插入”实现等真实工程场景的推荐写法。修改操作的全景增、改、并、删nlohmann/json 把 JSON 值建模为nlohmann::basic_json通常简写为json对已存在值的修改可分为四类操作面向类型主要 API语义追加/新增数组push_back、emplace_back、operator末尾追加元素插入/替换对象operator[]、emplace、push_back插入或整体覆盖键合并对象update、JSON Merge Patch把另一对象的成员拷入并覆盖删除/清空全部erase、clear移除成员/元素或清空值对于只读访问at、find、value等请参见官方文档 element access本文聚焦可变操作。所有修改接口都以json方式就地作用于调用对象无需重新赋值除少数显式重载外。贯穿全文的一个重要设计null的隐式转换。未初始化的json j;其类型是null。库中多数“插入类”函数在检测到调用目标是null时会先把它就地转换成对应容器数组或空对象再执行插入因此可以用一条语句从零开始构建结构见下文各节。向数组追加元素push_back 与 emplace_back基本用法与 operator向数组追加元素最直接的方式是push_back。从官方示例可以看到null到数组的自动转换json j; // null j.push_back(1); // [1] j.push_back(2); // [1,2] j.emplace_back(3); // [1,2,3] // operator is a shorthand for push_back j 4; // [1,2,3,4]j初始为null第一次push_back(1)时被隐式转换为[ ]再追加元素所以不必先手动j json::array()。operator等价于push_back是追加标量/值的最紧凑写法。对应完整可运行示例可在 examples/push_back.cpp 中找到其渲染输出见 examples/push_back.output。push_back 的三组重载从 API 文档 push_back.md 看push_back实际有 3 组重载// (1) 追加元素到数组末尾 void push_back(basic_json val); void push_back(const basic_json val); // (2) 把对象元素插入 JSON 对象object_t::value_type 即 key-value 对 void push_back(const typename object_t::value_type val); // (3) 用初始化列表调用 void push_back(initializer_list_t init);值得说明的是重载 (3)当当前值是对象、初始化列表恰好含两个元素、且第一个元素是字符串时该列表会被解释为一个对象的键值对插入否则被转换为普通 JSON 值按数组语义追加。这种二义性处理源自历史 issue见库注释目的是让{{key, value}}之类写法能正确落到对象语义上。对应示例见 examples/push_back__object_t__value.cpp 与 examples/push_back__initializer_list.cpp。emplace_back就地构造emplace_back不要求先构造好basic_json而是直接把可变参数args转发给basic_json的某个构造函数在数组末尾就地构造并返回新元素的引用templateclass... Args reference emplace_back(Args ... args);这在需要追加“由多个来源组装的值”时能省去一次临时对象拷贝/移动。例如追加一个由初始化列表构造的嵌套对象j.emplace_back(json{{x, 1}, {y, 2}})。若调用目标为null同样先转为空数组。自 3.7.0 起该函数返回被插入元素的引用更早版本无返回值见 emplace_back.md。复杂度与异常数组语义接口复杂度非法类型抛出push_back(1)/(3) 追加到数组平摊常数type_error.308cannot use push_back() with typepush_back(2) 插入对象O(log(size()))type_error.308emplace_back平摊常数type_error.311cannot use emplace_back() with type这些异常码与消息在实现中被集中抛出例如 include/nlohmann/json.hpp 中JSON_THROW(type_error::create(308, ...))。也就是说对number、string等非数组也非null的值调用追加接口会直接抛出type_error而不是“自动转型”。迭代器失效规则值得在循环中留意向数组追加可能引发重新分配reallocation此时全部迭代器含end()与元素引用均失效若未发生重分配仅end()失效。对于使用ordered_json的场景向对象追加成员也可能触发重新分配并使所有迭代器、引用失效见 push_back.md 与 emplace_back.md。若需在遍历的同时插入建议先收集再批量修改或使用索引访问。向对象添加与替换成员operator[] 与 emplaceoperator[]插入即替换对象修改最常用的方式是operator[]键不存在则插入存在则整体替换对应值这是官方的推荐入口json j; j[name] Mary; // {name:Mary} j[name] John; // {name:John} (replaced)operator[]同时服务于“读取 写入”对不存在的键做写访问会创建一个值为null的新成员其完整语义见 API 文档 operator[]。这种“插入或覆盖”语义适合大多数场景但注意它总是整体替换如果旧值是一个对象而新值是标量旧对象内容会被直接丢弃不会逐层合并。emplace仅当键缺失时插入add-if-absent如果业务需求是“只在键不存在时写入已存在则跳过”应使用emplacetemplateclass... Args std::pairiterator, bool emplace(Args ... args);它把args就地构造成一个对象成员仅当容器中尚不存在该键才插入返回值中bool表示是否真的发生了插入iterator指向新插入元素或已存在的同名元素json j; auto [it, inserted] j.emplace(a, 1); // inserted true auto [it2, inserted2] j.emplace(a, 2); // inserted2 false, a 仍为 1这是实现“合并时不覆盖用户显式设置”之类语义的便捷工具。若调用目标为null会先隐式转为空对象。相关特性复杂度 O(log(size()))异常时提供强异常保证strong guarantee抛出异常则任何 JSON 值都不变对非对象或null以外的类型调用抛出type_error.311cannot use emplace() with number。从源码结构看对象成员实际存储于object_t默认为std::mapstd::string, json中因此emplace/按键插入的时间复杂度为 O(log n)与std::map的插入语义一致。合并对象update 的浅合并与递归深合并合并两个对象是配置文件合并、默认参数合并中最常见的操作。nlohmann/json 为此提供update其语义受 Python 的dict.update启发——把另一个对象的所有成员拷入重复键默认被覆盖。两种签名// (1) 从另一个 JSON 对象合并 void update(const_reference j, bool merge_objects false); // (2) 从同一 JSON 对象上的迭代器区间 [first, last) 合并 void update(const_iterator first, const_iterator last, bool merge_objects false);实现上重载 (1) 只是把参数转发给重载 (2)update(j.begin(), j.end(), merge_objects)见 include/nlohmann/json.hpp。merge_objectsfalse 与 true 的差别merge_objects false默认源对象中已存在的键被整体覆盖浅合并。merge_objects true当源对象中某键的值是对象且目标对象中该键的已有值也是对象时对该键递归执行合并其余情况值非对象、键不存在、已有值非对象仍按覆盖处理。官方示例 examples/update.cpp 同时演示了两种模式。它先构造两个对象json o1 R( {color: red, price: 17.99, names: {de: Flugzeug}} )_json; json o2 R( {color: blue, speed: 100, names: {en: plane}} )_json; json o3 o1; // add all keys from o2 to o1 (updating color, replacing names) o1.update(o2); // add all keys from o2 to o1 (updating color, merging names) o3.update(o2, true);输出examples/update.output清晰展现了差异{ color: blue, names: { en: plane }, price: 17.99, speed: 100 } { color: blue, names: { de: Flugzeug, en: plane }, price: 17.99, speed: 100 }第一个输出中names被o2的值整体替换de丢失第二个输出中names被递归合并de与en共存。实现机理何时才递归update的合并逻辑可以在 include/nlohmann/json.hpp 中看到for (auto it first; it ! last; it) { if (merge_objects it.value().is_object()) { auto it2 m_data.m_value.object-find(it.key()); // Only recurse when the existing value is itself an object. // Otherwise overwrite, matching the documented all other values // are overwritten as usual behavior (see #5402). if (it2 ! m_data.m_value.object-end() it2-second.is_object()) { it2-second.update(it.value(), true); // ... JSON_DIAGNOSTICS 下维护 m_parent continue; } } m_data.m_value.object-operator[](it.key()) it.value(); }两个细节值得注意递归发生的前提是两端同键的值都必须是对象若已有值不是对象即使源值是对象也会整体覆盖这与官方文档注释#5402描述的“all other values are overwritten as usual”一致。若目标对象中该键不存在则直接插入源值含嵌套对象不会无谓递归。此外update被调用在一个null值上时会先把null转为空对象见 include/nlohmann/json.hpp这与push_back的null转换策略保持一致。工程场景默认配置与用户配置合并这是 update.md 给出的典型用例。应用默认设置如下{ color: red, active: true, name: {de: Maus, en: mouse} }用户选择性覆盖{ color: blue, name: {es: ratón} }先浅合并再深合并分别得到auto user_settings json::parse(config.json); auto effective_settings get_default_settings(); effective_settings.update(user_settings); // 默认浅合并重复键整体覆盖 // effective_settings.update(user_settings, true); // 深合并对象键逐层合并默认合并结果name被整体替换为{es: ratón}de/en丢失active因用户未设置而保留。深合并结果merge_objects truename变为{de: Maus, en: mouse, es: ratón}。update 的边界条件与语义摘要类型约束只能在对象上调用对非对象抛出type_error.312cannot use update() with string。区间版额外要求first与last属于同一 JSON 对象否则抛出invalid_iterator.210iterators do not fit。复杂度两种重载均为 O(N·log(size()N))N 为待插入元素个数。异常安全basic guarantee——若中途抛出异常值可能被部分修改。版本update自 3.0.0 加入merge_objects参数在 3.10.5 引入。使用ordered_json时向对象添加成员可能触发重新分配使全部迭代器与引用失效。update 与其他合并机制的边界update负责“把另一个完整对象合进来”。如果你需要的是结构化的差异修改nlohmann/json 还提供两条更规范的路径官方文档分别有独立专题详见文末延伸阅读JSON Merge PatchRFC 7386把“待修改补丁”整体应用到一个文档上天然是递归合并语义与update(obj, true)的应用目标互补patch 中显式置null表示删除键。JSON PatchRFC 6902用一系列明确定义的编辑操作add/remove/replace/move/copy/test配合json_pointer定位并修改文档。删除元素erase 的多种形态删除元素由erase完成它按调用目标与参数形态共有 5 组重载// (1) 按迭代器删除单个元素 iterator erase(iterator pos); const_iterator erase(const_iterator pos); // (2) 按迭代器区间删除 [first, last) iterator erase(iterator first, iterator last); // (3) 按键删除对象成员 size_type erase(const typename object_t::key_type key); // (4) 透明比较器按键删除3.11.0可与 string_view 等键类型比较 templatetypename KeyType size_type erase(KeyType key); // (5) 按下标删除数组元素 void erase(const size_type idx);官方示例给出最常用的两种形态json j {{a, 1}, {b, 2}, {c, 3}}; j.erase(b); // {a:1,c:3} json a {1, 2, 3, 4}; a.erase(1); // [1,3,4] (erase by index)各形态的关键语义按键 (3)/(4)仅对象可用返回值是实际删除的元素个数默认object_t为std::map时恒为 0 或 1。透明比较版本允许传入std::string_view之类的键类型而避免临时std::string构造C17。按下标 (5)仅数组可用复杂度与被删元素到数组末尾的距离成线性后续元素需前移下标越界idx size()抛出out_of_range.401如array index 17 is out of range。按迭代器 (1)/(2)可用在数组、对象等上pos必须是有效且可解引用的迭代器不能是end()。特别地如果在除null外的原始类型primitive如number/string上调用迭代器版值会被置为null——这是该接口为保持统一返回迭代器语义而做出的行为使用时需留意。对null调用任何形态都会抛出type_error.307cannot use erase() with null迭代器不属于当前值则抛invalid_iterator系列异常202/203/204/205。异常安全strong guarantee异常时原值保持完好。迭代器失效对象按迭代器/键删除会失效被删元素相关的引用与迭代器数组按迭代器删除会使被删位置及之后的迭代器、引用含end()失效。对应可运行示例包括 examples/erase__IteratorType.cpp、examples/erase__object_t_key_type.cpp 与 examples/erase__size_type.cpp。清空但保留类型clearclear的语义与erase不同它清空内容但保留当前 JSON 类型并把值重置为该类型的“默认值”等价于*this basic_json(type())void clear() noexcept;当前类型clear 后的值nullnullbooleanfalsestringnumber0binary空字节数组object{}array[]该函数声明为noexcept实现见 single_include/nlohmann/json.hpp 对应入口官方文档标注其具有不抛异常保证no-throw guarantee复杂度为 O(size)。注意它会失效与该值相关的全部迭代器、指针与引用。典型用途是把已用过的数组/对象“掏空复用”而不改变其在父结构中的位置与类型例如清空一个日志缓冲数组使其继续保持数组类型、可被再次push_back。如何把这些修改接口组合使用综合示例可直接编译运行#include iostream #include nlohmann/json.hpp using json nlohmann::json; int main() { json doc json::object(); // 显式创建对象 // 数组从 null 起步逐条追加 json tags; tags.emplace_back(cpp); tags json; doc[tags] std::move(tags); // 移入避免拷贝 // 对象仅在缺失时插入默认值 doc.emplace(retries, 3); // 合并把一段用户覆盖合入默认配置深合并 json defaults {{retries, 5}, {name, {en, default}}}; doc.update(defaults, false); // 已有键被覆盖 doc.update(R({name:{zh:示例}})_json, true); // 递归合并 name // 删除与清空 doc.erase(retries); // 按键删除 doc[tags].clear(); // 清空但保持数组类型 std::cout doc.dump(2) \n; // 输出剩余结构 }工程实践中几条建议构建大数组时优先emplace_back/而非反复operator[]代码更贴近“顺序追加”语义“add if absent”务必用emplace而非先查后写既省一次查找又天然线程内原子单线程下合并配置时先想清楚要浅合并还是深合并update第二参数merge_objects是浅/深的分水岭若要长期持有对成员对象的引用如auto x j[cfg];后续对该父对象执行插入/删除前要意识到可能引发的失效大范围的批量差异修改优先考虑 JSON Merge Patch 与 JSON Patch而不是手写多行eraseupdate。延伸阅读创建值创建对象、数组、字面量_json等基础见 creating_values只读取值见 element access 与 basic_json API 总览各接口 API 参考push_back、emplace_back、emplace、update、erase、clear结构化批量修改JSON Merge Patch 与 JSON Patch Diff、json_pointer修改类接口的实现集中位于 include/nlohmann/json.hppupdate/erase/clear同处该文件单头版本见 single_include/nlohmann/json.hpp相关单元测试可在 tests/src/unit-modifiers.cpp 中探索。【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价