news 2026/9/8 22:31:55

nlohmann/json 的 basic_json::emplace:向 JSON 对象原地构造并安全插入新成员

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
nlohmann/json 的 basic_json::emplace:向 JSON 对象原地构造并安全插入新成员

nlohmann/json 的 basic_json::emplace:向 JSON 对象原地构造并安全插入新成员

【免费下载链接】jsonJSON for Modern C++项目地址: https://gitcode.com/GitHub_Trending/js/json

JSON for Modern C++(nlohmann/json)为basic_json提供了丰富的"修改值"接口,其中emplace()是向JSON 对象按 key 原地构造并插入成员的核心成员函数。本指南以官方 API 文档为骨架,结合 json.hpp 源码实现与 unit-modifiers.cpp 单元测试,系统讲解emplace()的签名、null 自动转换语义、返回的(iterator, bool)判定规则、type_error.311异常边界与ordered_json下的迭代器失效细节,并给出可直接编译运行的完整示例,帮助你准确区分它与insertpush_backemplace_backoperator[]的适用场景。

函数签名与核心语义

emplace()的完整声明定义于 json.hpp,是basic_json的一个成员模板函数:

template<class... Args> std::pair<iterator, bool> emplace(Args&& ... args);

它的工作方式与标准库关联容器的emplace完全一致,但作用对象是basic_json值本身,核心语义为:

  • JSON 对象上,用给定的args就地构造(in-place construct)一个新成员,并且仅当容器中不存在相同 key 的元素时才真正插入
  • 如果被调用的 JSON 值当前是#!json null,会先静默创建一个空对象,再向其中追加由args构造出的值;
  • 若 key 已存在,则不发生任何修改,args也不会被构造为元素。

对底层的std::map(默认object_t)而言,这意味着emplace避免了"先构造临时basic_json、再由insert拷贝/移动"的中间步骤——构造直接发生在为对象元素分配好的存储空间内。

模板参数与普通参数

成员含义
模板参数Args一组兼容类型,用于构造一个basic_json对象(例如string_t、数值、bool、数组/对象、嵌套basic_json等)
args(in)转发给某个basic_json构造函数的一组实参,通过完美转发(perfect forwarding,Args&& ...)向下传递

由于args直接参与basic_json的构造函数重载决议,标准对象构造规则在这里都适用。例如传入(3, "foo")会被解析为构造一个包含 3 个"foo"的数组值——这正是 emplace 与operator[]/insert在构造时机上的差异所在。

返回值:(iterator, bool)双重语义

与标准库关联容器一致,emplace()返回std::pair<iterator, bool>

  • first:指向被插入元素的迭代器;如果 key 已存在导致未发生插入,则指向已存在的那个元素(即原值不会被替换);
  • second#!cpp bool布尔标志,true表示本次确实发生了插入,false表示 key 已存在、未做任何修改。

官方文档 emplace.md 将此表述为"由指向已插入元素或(若未插入)已存在元素的迭代器,与一个表示是否发生插入的布尔值组成的 pair"。在默认object_t = std::map下,判断"已存在"的依据是对象当前使用的比较器(默认字典序比较 key)。

迭代器失效规则

对于普通basic_json(默认底层为std::map),emplace()不会使既有迭代器与引用失效。

但文档特别强调了一种例外:当使用 ordered_json(底层为 ordered_map.hpp 中的ordered_map,本质是基于 vector 的顺序容器)时,向对象追加新成员可能触发重分配(reallocation),此时:

所有迭代器(包括end()迭代器)以及所有指向既有元素的引用都会失效。

这与 ordered_map.hpp 的实现直接相关:ordered_map::emplace先做一次线性查找判断 key 是否已存在,再调用底层Container::emplace_back把元素追加到末尾,因此一旦容量不足发生扩容,迭代器与引用即全部失效。从源码结构看,这也是ordered_json相比默认std::map在插入语义上最需要留意的差异——前者保序但存在扩容失效,后者保证引用稳定性但按键排序。

异常安全与异常类型

emplace()提供强异常保证(strong guarantee):如果构造过程中抛出异常,任何 JSON 值都不会发生改变——不会出现"对象已从 null 变成 object,但成员没有插入"之类的半成品状态。

type_error.311

当在既非 JSON 对象也非#!json null的值(如数值、数组、字符串、布尔)上调用emplace()时,会抛出json::type_error,异常标识为311,完整定义见 exceptions.md:

[json.exception.type_error.311] cannot use emplace() with number

消息中的number会随实际类型名变化(如cannot use emplace() with array)。注意文档明确指出emplace()仅接受object 或 null,这一点与面向数组的emplace_back()(接受 array 或 null)正好互补,两者的类型检查错误共用 311 编号。

对应源码中的守卫逻辑位于 json.hpp:

// emplace only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) { JSON_THROW(type_error::create(311, detail::concat("cannot use emplace() with ", type_name()), this)); }

若通过,则当值为 null 时,源码会先把内部类型标记与存储区切换为空对象(json.hpp):

// transform a null object into an object if (is_null()) { m_data.m_type = value_t::object; m_data.m_value = value_t::object; assert_invariant(); }

复杂度

  • 默认object_tstd::map):对数复杂度 O(log(size())),因为底层是红黑树,插入与查找均为对数时间。
  • 需要再次提醒:ordered_json的底层 ordered_map 使用线性查找 + 末尾追加,实际复杂度是 O(n),官方文档给出的 O(log(size())) 针对的是默认关联容器实现。

完整示例与输出解读

官方示例 emplace.cpp 是学习该 API 的最佳起点,它同时演示了 null 自动转对象、重复 key 不覆盖两个关键行为:

#include <iostream> #include <nlohmann/json.hpp> using json = nlohmann::json; int main() { // create JSON values json object = {{"one", 1}, {"two", 2}}; json null; // print values std::cout << object << '\n'; std::cout << null << '\n'; // add values auto res1 = object.emplace("three", 3); null.emplace("A", "a"); null.emplace("B", "b"); // the following call will not add an object, because there is already // a value stored at key "B" auto res2 = null.emplace("B", "c"); // print values std::cout << object << '\n'; std::cout << *res1.first << " " << std::boolalpha << res1.second << '\n'; std::cout << null << '\n'; std::cout << *res2.first << " " << std::boolalpha << res2.second << '\n'; }

编译并运行(使用单头文件版本即可,例如g++ -std=c++11 -I single_include emplace.cpp):

{"one":1,"two":2} null {"one":1,"three":3,"two":2} 3 true {"A":"a","B":"b"} "b" false

输出可以逐行对照理解:

  1. 前两行是初始状态:一个普通对象与一个值为null的 JSON 值;
  2. object.emplace("three", 3)成功后,对象多出"three":3,返回迭代器指向3secondtrue
  3. 两个对nullemplace("A", "a")emplace("B", "b")调用把null静默升级成对象{"A":"a","B":"b"}——整个过程无需你预先判断或手工初始化;
  4. 最后null.emplace("B", "c")因为 key"B"已存在而没有覆盖原值:*res2.first仍打印"b"(原有元素),second打印false

测试用例对行为的验证

单元测试 unit-modifiers.cpp 的emplace()小节对上述语义做了逐条断言,可作为行为契约来阅读:

  • 从 null 起步:默认构造的json j;依次emplace("foo", "bar")emplace("baz", "bam"),测试断言res1.second == truej.type() == json::value_t::object(null 已转型);再emplace("baz", "bad")时断言res3.second == false*res3.first == "bam"(原值未被替换),最终对象等于{{"baz","bam"},{"foo","bar"}}
  • 在已有对象上插入json j = {{"foo", "bar"}}emplace("baz", "bam")成功(second == true),重复插入"foo"失败且既有"bar"保持不变;
  • 非法类型json j = 1;j.emplace("foo", "bar")抛出异常,断言消息精确匹配[json.exception.type_error.311] cannot use emplace() with number

与相关接口的分工对照

接口适用容器返回值行为要点
emplace()object / nullpair<iterator, bool>按 key 就地构造,key 已存在则不插入不覆盖;null 先转对象
emplace_back()(emplace_back.md)array / nullreference就地构造后追加到数组末尾,null 先转数组;自 3.7.0 起返回引用,摊还常数复杂度
insert()(insert.md)array / objectiteratorvoid把已构造好的值按迭代器位置 / key 范围插入
operator[]/push_back()object / array引用 /reference前者在 key 不存在时默认构造空值并返回引用,可用于赋值

从对象语义看,emplace()最接近"键存在即放弃,绝不自作主张覆盖"的原子判空操作,适合实现幂等的成员装配逻辑;而operator[]则会为缺失的 key 就地创建一个空值(通常是null)供随后赋值,两者存在本质差别。更系统的修改场景梳理可参考特性指南 modifying_values.md。

版本历史与兼容性

  • 2.0.8版本起提供emplace(),签名与语义保持向后兼容;
  • 源码实现基于类型检查 + null 转型 + 底层容器转发三步完成(见 json.hpp),配合set_parent处理基于std::unique_ptr的父指针追踪(SAX/串行化子值归属管理)。

使用建议小结

  • 需要不覆盖已有键地向对象写入数据、且希望在插入点就地构造值避免多余临时对象时,优先使用emplace()
  • null值调用是安全的,库会自动把它升级为对象,但请在逻辑上意识到这种隐式转型;
  • 读取返回值时务必同时消费second:只有它为true时才真正新增了元素;
  • 遍历或持有ordered_json的迭代器期间反复调用emplace(),要考虑底层 vector 扩容导致的整体失效;
  • 不要期望emplace()能修改已存在键的值——那是operator[]insert_or_assign类语义的职责。

【免费下载链接】jsonJSON for Modern C++项目地址: https://gitcode.com/GitHub_Trending/js/json

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 22:30:49

51单片机用74HC595驱动8位数码管:原理与C51代码详解

简介&#xff1a;面向51单片机初学者的开发板实验资料&#xff0c;演示如何利用HC595移位寄存器扩展输入输出口&#xff0c;进而驱动八个数码管显示&#xff0c;解决单片机直接驱动多位数码管时引脚不足的常见问题。整个压缩包共包含十三个文件&#xff0c;大小约三百四十三KB&…

作者头像 李华
网站建设 2026/9/8 22:30:48

Claude Code 插件精选:从 30+ 到 9 款生产力工具的筛选与配置实战

Claude Code 的插件生态&#xff0c;这两年膨胀得比我手机里的相册还快。GitHub 上随便一搜就是几千个仓库&#xff0c;各种“神器”“必装”“让 Claude 起飞”的标题满天飞。我入坑不算早&#xff0c;但也交了不少学费——早期看到什么装什么&#xff0c;光插件列表就堆了三十…

作者头像 李华
网站建设 2026/9/8 22:30:24

btop 终端显卡监控完整指南:NVIDIA、AMD、Intel 一屏看全

btop 终端显卡监控完整指南&#xff1a;NVIDIA、AMD、Intel 一屏看全 【免费下载链接】btop A monitor of resources 项目地址: https://gitcode.com/GitHub_Trending/bt/btop 如果你想在终端里把 NVIDIA、AMD、Intel 三家显卡的运行状态一次看全&#xff0c;btop 的显卡…

作者头像 李华
网站建设 2026/9/8 22:27:46

树莓派Pico ADC实战:从machine.ADC到定时温度采集与ISR避坑

先说个我自己的经历。第一次拿树莓派 Pico 玩 ADC&#xff0c;接了个 10k 电位器到 ADC0&#xff0c;读回来的数值在 18000 到 21000 之间乱跳&#xff0c;一开始我还以为板子坏了或者线没接好。后来把输入阻抗、参考电压、SAR 采样原理这些东西捋清楚&#xff0c;才发现这颗芯…

作者头像 李华
网站建设 2026/9/8 22:27:17

FPGA实现图像电子透雾的硬件化设计与工业落地

1. 项目概述&#xff1a;为什么在FPGA上做图像电子透雾不是“炫技”&#xff0c;而是工程刚需我第一次在煤矿井下调试高清视频监控系统时&#xff0c;被现场工程师拉到屏幕前指着一段实时画面说&#xff1a;“你看这雾&#xff0c;不是天气问题&#xff0c;是煤尘水汽LED补光混…

作者头像 李华