首页
/ nlohmann::basic_json::erase:JSON for Modern C++ 删除元素的五重载全景与源码级解析

nlohmann::basic_json::erase:JSON for Modern C++ 删除元素的五重载全景与源码级解析

2026-09-06 18:06:54作者:霍妲思

在 JSON for Modern C++(nlohmann/json)中,nlohmann::basic_json::erase 是"修改值"(Modifying values)一组成员,用于删除对象成员、数组元素以及通过迭代器定位的任意 JSON 值。它提供 5 个重载:按迭代器删除单元素、按迭代器区间删除、按对象键删除(含 C++17 std::string_view 异构键版本)、按数组下标删除。读完本文,你将掌握每个重载的适用类型、返回值、异常编码(307/202/203/204/205/401)、复杂度与迭代器失效规则,并能在实际项目中写出可复制运行的删除代码。

一、方法签名总览

erase 的 5 个重载声明如下(见 API 参考文档):

// (1) 按迭代器删除单个元素
iterator erase(iterator pos);
const_iterator erase(const_iterator pos);

// (2) 按迭代器区间 [first, last) 删除
iterator erase(iterator first, iterator last);
const_iterator erase(const_iterator first, const_iterator last);

// (3) 按对象键删除
size_type erase(const typename object_t::key_type& key);

// (4) 异构键删除(C++17 下可用 string_view 等可比较键)
template<typename KeyType>
size_type erase(KeyType&& key);

// (5) 按数组下标删除
void erase(const size_type idx);

其中模板参数 KeyType 是一个不同于 json_pointer 的对象键类型,需满足能与 string_t 通过 object_comparator_t 比较;在 C++17 下它也可以是 string view。

二、五个重载的语义逐条解析

(1) 按迭代器删除单个元素

pos 必须有效且可解引用,因此 end()(有效但不可解引用)不能作为 posnull 以外的任何原始类型调用,结果 JSON 值会被置为 null——这是对"删除一个标量/字符串/二进制整体"的语义约定,而不是删除其内部某个字符。

(2) 按迭代器区间删除 [first, last)

first == lastfirst 不必可解引用,删除空区间是一个无操作(no-op)。对 null 以外的原始类型调用,同样会把整个值置为 null

(3)/(4) 按对象键删除

从 JSON 对象中按键删除成员。重载 (4) 是模板版本,仅在 KeyTypetypename object_t::key_type 可比较、且 typename object_comparator_t::is_transparent 表示一个类型时才可用(即底层容器支持异构查找),它返回"实际删除的元素个数":默认 std::map 实现下恒为 0(键不存在)或 1(键存在)。

(5) 按数组下标删除

从 JSON 数组中按下标 idx 删除元素,无返回值。仅在数组上可用,idx >= size() 时抛出 out_of_range.401

三、返回值

重载 返回值
(1) 紧随最后一个被删元素之后的迭代器;若 pos 指向最后一个元素,则返回 end()
(2) 紧随最后一个被删元素之后的迭代器;若 last 指向最后元素,则返回 end()
(3) 删除的元素个数;默认 std::map 时恒为 01
(4) 同 (3)
(5) 无(void

异常安全:提供强异常安全保证(strong exception safety)——若发生异常,原始值保持完整。

四、异常行为对照表

结合 异常文档,5 个重载可能抛出的异常如下:

重载 异常 触发条件 典型消息
(1) type_error.307 null 值调用 "cannot use erase() with null"
(1) invalid_iterator.202 迭代器不属于当前 JSON 值 "iterator does not fit current value"
(1) invalid_iterator.205 对原始类型使用了非 begin() 的迭代器 "iterator out of range"
(2) type_error.307 null 值调用 "cannot use erase() with null"
(2) invalid_iterator.203 迭代器不属于当前 JSON 值 "iterators do not fit current value"
(2) invalid_iterator.204 对原始类型使用了非 begin()/end() 边界之外的迭代器 "iterators out of range"
(3)(4) type_error.307 对非对象类型调用 "cannot use erase() with null"
(5) type_error.307 对非数组类型调用 "cannot use erase() with null"
(5) out_of_range.401 idx >= size() "array index 17 is out of range"

五、复杂度

重载 复杂度
(1) 对象:摊还常数;数组:与 pos 到容器末尾的距离成线性;字符串/二进制:与成员长度成线性;其他类型:常数
(2) 对象:log(size()) + std::distance(first, last);数组:与 firstlast 的距离线性,再加与 last 到末尾的距离线性;字符串/二进制:与成员长度成线性;其他:常数
(3)(4) log(size()) + count(key)
(5) idx 到容器末尾的距离成线性

迭代器失效规则(Notes):

  • 重载 (1):会使 erase 点处及之后的迭代器与引用失效,包括 end() 迭代器;
  • 重载 (2):无特别失效说明;
  • 重载 (3)(4):仅被删元素的引用和迭代器失效,其他不受影响;
  • 重载 (5):无特别失效说明。

六、可运行示例全收录

以下 5 个示例与 API 文档 一一对应,均基于 示例源文件 可独立编译运行。

示例 (1):按迭代器删除元素

erase__IteratorType.cpp 演示了 erase(iterator) 在 6 种 JSON 类型上的效果:

#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main()
{
    // create JSON values
    json j_boolean = true;
    json j_number_integer = 17;
    json j_number_float = 23.42;
    json j_object = {{"one", 1}, {"two", 2}};
    json j_array = {1, 2, 4, 8, 16};
    json j_string = "Hello, world";

    // call erase()
    j_boolean.erase(j_boolean.begin());
    j_number_integer.erase(j_number_integer.begin());
    j_number_float.erase(j_number_float.begin());
    j_object.erase(j_object.find("two"));
    j_array.erase(j_array.begin() + 2);
    j_string.erase(j_string.begin());

    // print values
    std::cout << j_boolean << '\n';
    std::cout << j_number_integer << '\n';
    std::cout << j_number_float << '\n';
    std::cout << j_object << '\n';
    std::cout << j_array << '\n';
    std::cout << j_string << '\n';
}

实际输出(erase__IteratorType.output):

null
null
null
{"one":1}
[1,2,8,16]
null

可以看到:布尔、整数、浮点、字符串被整体置为 null;对象删掉 "two" 键;数组删掉下标 2 的元素 4

示例 (2):按迭代器区间删除

erase__IteratorType_IteratorType.cpp 演示 erase(first, last)

    // call erase()
    j_boolean.erase(j_boolean.begin(), j_boolean.end());
    j_number_integer.erase(j_number_integer.begin(), j_number_integer.end());
    j_number_float.erase(j_number_float.begin(), j_number_float.end());
    j_object.erase(j_object.find("two"), j_object.end());
    j_array.erase(j_array.begin() + 1, j_array.begin() + 3);
    j_string.erase(j_string.begin(), j_string.end());

输出(erase__IteratorType_IteratorType.output):

null
null
null
{"one":1}
[1,8,16]
null

数组中区间 [begin()+1, begin()+3)(即 2, 4)被移除,剩下 [1, 8, 16]

示例 (3):按对象键删除

erase__object_t_key_type.cpp 演示键删除及返回值:

    // create a JSON object
    json j_object = {{"one", 1}, {"two", 2}};

    // call erase()
    auto count_one = j_object.erase("one");
    auto count_three = j_object.erase("three");

    std::cout << j_object << '\n';
    std::cout << count_one << " " << count_three << '\n';

输出(erase__object_t_key_type.output):

{"two":2}
1 0

返回值 1 表示 "one" 存在并被删除,0 表示 "three" 不存在。

示例 (4):C++17 下用 string_view 作为键

erase__keytype.c++17.cpp(版本 3.11.0 起提供)展示了重载 (4) 的异构键能力:

#include <string_view>
#include <nlohmann/json.hpp>

using namespace std::string_view_literals;
using json = nlohmann::json;

int main()
{
    json j_object = {{"one", 1}, {"two", 2}};

    // 直接用 string_view 字面量作为键
    auto count_one = j_object.erase("one"sv);
    auto count_three = j_object.erase("three"sv);

    std::cout << j_object << '\n';
    std::cout << count_one << " " << count_three << '\n';
}

输出与 (3) 相同(erase__keytype.c++17.output):{"two":2}1 0

示例 (5):按数组下标删除

erase__size_type.cpp

    json j_array = {0, 1, 2, 3, 4, 5};

    j_array.erase(2);   // 删除下标 2 的元素

    std::cout << j_array << '\n';

输出(erase__size_type.output):

[0,1,3,4,5]

七、源码级实现解析

以下分析基于 include/nlohmann/json.hpp(单头文件版本,与 single_include/nlohmann/json.hpp 内容一致)。

7.1 迭代器删除:类型分派 + 原始类型整体置空

模板实现 erase(iterator) 的骨架是:

// make sure the iterator fits the current value
if (JSON_HEDLEY_UNLIKELY(this != pos.m_object))
{
    JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this));
}

先校验迭代器绑定的宿主对象是否为当前 JSON 值,否则抛 invalid_iterator.202——这解释了为什么用"别的 json 对象的迭代器"删除会失败。

随后按 m_data.m_type 分派:

  • 布尔/整数/浮点/无符号/字符串/二进制:若 pos 不是 begin()primitive_iterator.is_begin()),抛 invalid_iterator.205;否则通过分配器释放 string/binary 内存(L2524-L2537),最后把 m_data.m_type 改为 value_t::null——这就是"原始类型 erase 后变 null"的底层来源;
  • 对象:转发给内部 std::maperase(pos),返回其后的迭代器(L2544-L2548);
  • 数组:转发给内部 std::vectorerase(pos)L2550-L2554);
  • null/discarded:抛 type_error.307L2556-L2559)。

7.2 区间删除:203/204 与空区间 no-op

模板实现 erase(first, last) 要求两个迭代器都绑定到当前对象(this != first.m_object || this != last.m_object 则抛 invalid_iterator.203)。对原始类型,要求 first 必须是 begin()last 必须是 end()L2588-L2593),否则抛 invalid_iterator.204;这从实现层面保证了 first == last 的空区间只在合法边界上生效(no-op)。对象与数组分支直接转发给底层容器的区间 eraseL2615-L2627)。

7.3 键删除:非模板重载 + SFINAE 内部转发

非模板 erase(key) 有一个值得注意的设计:

size_type erase(const typename object_t::key_type& key)
{
    // the indirection via erase_internal() is added to avoid making this
    // function a template and thus de-rank it during overload resolution
    return erase_internal(key);
}

源码注释明确说明:通过 erase_internal() 间接调用,是为了避免该重载变成模板从而在重载决议中"降权"(de-rank),确保非模板版本优先于模板版本 (4) 被选中。

内部 erase_internaldetail::has_erase_with_key_type 做 SFINAE 分派两条路径:

  • object_t 原生支持该键类型(如默认 std::map 在 C++14 起的透明比较),直接 object->erase(key)
  • 否则回退为 find + 单迭代器 erase,找到返回 1,否则返回 0L2662-L2668)。

两条路径都会先检查 is_object(),非对象一律抛 type_error.307。这正是"返回值恒为 0 或 1"的出处。

7.4 下标删除:仅数组 + 越界 401

void erase(size_type idx) 的逻辑很直白:

if (JSON_HEDLEY_LIKELY(is_array()))
{
    if (JSON_HEDLEY_UNLIKELY(idx >= size()))
    {
        JSON_THROW(out_of_range::create(401,
            detail::concat("array index ", std::to_string(idx), " is out of range"), this));
    }
    m_data.m_value.array->erase(m_data.m_value.array->begin() + static_cast<difference_type>(idx));
}
else
{
    JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this));
}

它把下标转成 begin() + idx 后交给底层 std::vector::erase,因此复杂度与 idx 到末尾的距离成线性,与"复杂度"一节吻合。

相关测试覆盖在 tests/src/unit-modifiers.cpp(修改类成员的单测集中文件),可用于验证本文各异常与返回值描述的行为。

八、版本历史与相关 API

版本沿革(来自 API 文档):

重载 版本
(1) 1.0.0 起提供;3.8.0 增加对二进制类型的支持
(2) 1.0.0 起提供;3.8.0 增加对二进制类型的支持
(3) 1.0.0 起提供
(4) 3.11.0 起提供
(5) 1.0.0 起提供

相关接口:

  • clear:清空整个 JSON 值的内容;
  • insert:向数组/对象插入值(与 erase 方向相反的操作);
  • Modifying values:修改值的功能专题文章。

九、使用要点小结

  1. 原始类型(布尔/数字/字符串/二进制),erase(begin())erase(begin(), end()) 的效果是把整个值置为 null,而不是"删掉一个字符";
  2. null 值调用任何重载都会抛 type_error.307,删除前建议先用 is_null() 防护;
  3. 按对象键删除返回值是删除个数(0/1),适合判断键是否实际存在;C++17 下可直接传 string_view(重载 4,3.11.0+)避免临时 std::string 构造;
  4. 数组按下标删除是 void 返回且线性移动尾部元素,频繁头部删除时优先考虑其他数据结构或使用 erase(iterator) 形式;
  5. 所有重载都提供强异常安全保证,出错时原值保持完整。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388