JSON for Modern C++ 深度解析:nlohmann::basic_json::clear() 的类型保持语义与源码实现
clear() 是 JSON for Modern C++(nlohmann/json,仓库内源码标注版本 3.12.0)中 nlohmann::basic_json 类的一个修改器(modifier)成员函数,它清空 JSON 值的内容但保留其类型。读完本篇,你可以准确掌握 clear() 对七种 JSON 值类型各自的重置结果、异常与复杂度保证,以及迭代器失效规则,并能在 include/nlohmann/json.hpp 的源码实现层面理解每种类型对应的底层清理动作。
函数签名与核心语义
官方 API 文档(docs/mkdocs/docs/api/basic_json/clear.md)给出的签名为:
void clear() noexcept;
其语义可以概括为:清空 JSON 值的内容,并将其重置为该类型对应的“默认值”,效果等价于:
*this = basic_json(type());
也就是说,clear() 调用后值仍然保持调用前的类型(通过 type() 返回的值),但内容被换成 basic_json(value_t) 构造函数为该类型生成的初始值。这是它与 std::vector::clear() 等容器清空操作的共同点,但由于 basic_json 是多态容器(一个变量可以是 object、array、string、number 等任意类型),需要逐类型定义“空”的含义。
各类型 clear() 之后的初始值
文档给出的完整类型—初始值对照表如下:
| 值类型(value_t) | clear() 之后的初始值 |
|---|---|
null |
null |
boolean |
false |
string |
"" |
number(整数/无符号/浮点) |
0 / 0 / 0.0 |
binary |
空字节序列(empty byte vector) |
object |
{} |
array |
[] |
需要注意两个细微之处:
- 文档表中
number统一写作0,但源码实现区分了number_integer(置0)、number_unsigned(置0)和number_float(置0.0)三种内部子类型,序列化时整数输出0、浮点输出0.0; binary类型清空后是空的字节容器,但仍保持binary类型——由于空二进制没有“非空”可言,empty()对清空后的 binary 返回false(测试用例中显式用CHECK(!j.empty())验证了这一点,见后文测试小节)。
完整可运行示例与输出
官方文档内置的示例(源文件 clear.cpp)演示了 clear() 对不同 JSON 类型的作用,完整代码如下:
#include <iostream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
// create JSON values
json j_null;
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 clear()
j_null.clear();
j_boolean.clear();
j_number_integer.clear();
j_number_float.clear();
j_object.clear();
j_array.clear();
j_string.clear();
// serialize the cleared values()
std::cout << j_null << '\n';
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';
}
运行输出(与 clear.output 一致):
null
false
0
0.0
{}
[]
""
可以看到:对象清空后是 {}、数组清空后是 []、字符串清空后是 ""、整数清空后是 0、浮点清空后是 0.0——类型全部保持不变,只有内容归零。
源码实现:按类型分发的清空逻辑
clear() 的实现位于单头文件 single_include/nlohmann/json.hpp(拆分源码中为 include/nlohmann/json.hpp),核心是一个按内部类型标签 m_data.m_type 分发的 switch 语句:
void clear() noexcept
{
switch (m_data.m_type)
{
case value_t::number_integer:
{
m_data.m_value.number_integer = 0;
break;
}
case value_t::number_unsigned:
{
m_data.m_value.number_unsigned = 0;
break;
}
case value_t::number_float:
{
m_data.m_value.number_float = 0.0;
break;
}
case value_t::boolean:
{
m_data.m_value.boolean = false;
break;
}
case value_t::string:
{
m_data.m_value.string->clear();
break;
}
case value_t::binary:
{
m_data.m_value.binary->clear();
break;
}
case value_t::array:
{
m_data.m_value.array->clear();
break;
}
case value_t::object:
{
m_data.m_value.object->clear();
break;
}
case value_t::null:
case value_t::discarded:
default:
break;
}
}
从源码结构可以读出几条实现层面的信息:
- 标量类型直接赋值:
number_integer、number_unsigned、number_float、boolean都是值语义成员,清空就是写入零值/false,不经过任何动态内存操作,因此可以做到noexcept; - 容器类型委托给 C++ 标准容器的
clear():string、binary、array、object底层分别是string_t、binary_t、array_t、object_t成员,clear()直接委托给它们的clear()方法释放元素; null与discarded是空操作:null本就无内容;discarded(解析错误时出现的“被丢弃的值”)同样保持原样。这正是实现*this = basic_json(type())等价语义的具体展开——库没有真正调用一次拷贝赋值,而是用分支赋值获得了更低开销的等价效果;- binary 只清空字节、不清除 subtype:
m_data.m_value.binary->clear()作用于底层容器byte_container_with_subtype(见 include/nlohmann/byte_container_with_subtype.hpp)。从源码结构看,该类的clear()继承自BinaryType(默认std::vector<std::uint8_t>),只会清空字节序列,而m_subtype/m_has_subtype成员并不随之重置;如果还需要去掉二进制子类型,应另行调用clear_subtype()。
异常安全、复杂度与迭代器失效
官方文档明确给出了三点契约:
- 异常安全:No-throw guarantee——
clear()永不抛出异常(与noexcept标注一致); - 复杂度:与 JSON 值的大小成线性关系。对字符串/数组/对象而言,线性开销来自逐个销毁容器元素(如对象的键值对、数组的元素),而标量类型是常数时间的赋值;
- 注意事项:所有与此容器相关的迭代器、指针和引用都会在
clear()之后失效。由于basic_json的迭代器会解引用出内嵌子 JSON 值(例如operator*返回const_reference),清空后任何持有迭代器或get_ptr/引用绑定旧元素的代码都必须丢弃。
单元测试:逐类型验证“值 == 同类型空值”
测试文件 tests/src/unit-modifiers.cpp 的 clear() SECTION 对每种类型都做了结构化断言,其验证模式高度一致:
SECTION("string")
{
json j = "hello world";
json const k = j;
j.clear();
CHECK(j == json(json::value_t::string)); // 内容等于“空字符串”
CHECK(j == json(k.type())); // 等于以原类型构造的默认值
}
即断言两条:其一,清空后的值等于用 value_t 标签构造的同类型默认值(json(json::value_t::string) 就是 "");其二,值等于 json(k.type()),其中 k 是清空前保存的副本——这正好从行为层面验证了文档中“等价于 *this = basic_json(type())”的语义描述。测试覆盖了 boolean、string、array(空/非空)、object(空/非空)、binary(空/非空)、number 整数/无符号/浮点、null 共九组场景,且对 binary 用例特意断言清空后 !j.empty() 且类型仍为 binary。这些测试是 clear() 各类型行为的最直接可验证依据。
版本历史
- 1.0.0 版本起提供
clear(); - 3.8.0 版本起支持
binary类型(此前的版本中clear()对 binary 没有分支)。
实践要点小结
- 当你需要“复用”一个
json变量(例如在循环中反复解析/构建同构 JSON)时,clear()是比整体赋值更便宜的清空方式:它保留类型标签,标量场景下只是几个字节的赋值; clear()不会改变类型,也不会改变 binary 值的 subtype;如需回到null或切换类型,应显式构造新的json;- 调用
clear()后必须使所有旧迭代器、get_ptr()返回的指针、以及通过get_ref取到的引用失效,否则会触发未定义行为。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00