JSON for Modern C++ 中 nlohmann::basic_json::empty() 的深度解析:返回值语义、源码实现与测试验证
本篇指南基于官方 API 文档,详解 nlohmann::basic_json::empty() 接口在 JSON for Modern C++(nlohmann/json)中的作用与语义:它如何判断一个 JSON 值"没有元素"、对九种值类型分别返回什么,并结合 库头文件 中的真实实现与 容量单元测试 验证其正确性。读完后,你将掌握该接口的返回值规则、源码级委托机制,以及它与 size()、迭代器定义之间的等价关系,能够在实际工程中安全地用它做容器状态检查。
函数签名与语义
empty() 的完整签名为:
bool empty() const noexcept;
官方定义是:检查一个 JSON 值是否没有元素,即其 size() 是否为 0。这是一个 const 成员函数,带有 no-throw 保证,可以在任何需要只读检查的场景中放心调用,包括范围检查、条件分支和断言。
从源码位置看,该函数位于 include/nlohmann/json.hpp 中 capacity(容量)功能组的开头,紧邻 size() 与 max_size(),与 C++ 标准容器的 capacity 接口组布局一致。
各值类型的返回值
官方文档给出了完整的返回值定义表:
| 值类型 | 返回值 |
|---|---|
| null | true |
| boolean | false |
| string | false |
| number | false |
| binary | false |
| object | object_t::empty() 的结果 |
| array | array_t::empty() 的结果 |
几个值得注意的点:
null是唯一"天然为空"的标量类型。null不持有任何数据,因此被视为空。- 所有其他标量类型(布尔、字符串、数字、二进制)一律返回
false。即使字符串内容长度为 0(即json j = "";),empty()也返回false——因为 JSON 容器本身持有一个 string 值,而不是"没有元素"。官方文档专门用 Notes 一节强调了这一点:empty()判断的是"JSON 容器本身是否为空",而不是"容器里存的字符串是否为空"。如需判断字符串内容长度,应先取回string_t再调用其自身的empty()。 - object 与 array 才是真正"有元素个数"的复合类型,其返回值委托给底层容器的
empty()。对于默认配置(array_t = std::vector<basic_json>、object_t = std::map<std::string, basic_json>),这意味着委托给标准库容器的 O(1)empty()。
源码实现解析
官方文档给出的"参考实现"非常简单:
bool empty() const noexcept
{
return size() == 0;
}
但 include/nlohmann/json.hpp 中的实际实现并没有走 size(),而是按 m_data.m_type 直接做类型分派,避免了一次间接调用:
bool empty() const noexcept
{
switch (m_data.m_type)
{
case value_t::null:
{
// null values are empty
return true;
}
case value_t::array:
{
// delegate call to array_t::empty()
return m_data.m_value.array->empty();
}
case value_t::object:
{
// delegate call to object_t::empty()
return m_data.m_value.object->empty();
}
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
// all other types are nonempty
return false;
}
}
}
可以从中读出三点实现细节:
basic_json是"类型标签 + 联合数据"结构。内部成员m_data.m_type记录当前值属于value_t枚举的哪一种,m_data.m_value中则存放对应的实际容器(array指针、object指针等)。empty()的第一层判断完全由类型标签驱动。- 委托而非计算。对 array/object,函数直接把判断交给
array_t/object_t的empty(),因此行为与所用容器类型严格一致。若用户通过模板参数改用 ordered_map 作为 object 容器(即ordered_json),从源码结构看ordered_map继承自std::vector<std::pair<const Key, T>>(见 include/nlohmann/ordered_map.hpp),其empty()同样是 O(1),空对象判断结果与std::map版本完全一致。 discarded类型归入"非空"分支。解析过程中被丢弃(discarded)的值与字符串、布尔、数字、二进制一样返回false,这与文档表中"其余类型返回false"的语义在实现上闭合。
与之对照,同文件中的 size() 实现采用同一套分派结构:null 返回 0,array/object 委托 size(),其余类型返回 1。因此文档中"empty() 即 size() == 0"的定义在两种实现路径下都成立——实际实现只是省去了 size() 的中间跳转,直接对类型标签分派。
完整示例与运行结果
官方示例(源码见 empty.cpp)覆盖 null、布尔、整数、浮点、对象、空对象、数组、空数组、字符串共九种情况:
#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_object_empty(json::value_t::object);
json j_array = {1, 2, 4, 8, 16};
json j_array_empty(json::value_t::array);
json j_string = "Hello, world";
// call empty()
std::cout << std::boolalpha;
std::cout << j_null.empty() << '\n';
std::cout << j_boolean.empty() << '\n';
std::cout << j_number_integer.empty() << '\n';
std::cout << j_number_float.empty() << '\n';
std::cout << j_object.empty() << '\n';
std::cout << j_object_empty.empty() << '\n';
std::cout << j_array.empty() << '\n';
std::cout << j_array_empty.empty() << '\n';
std::cout << j_string.empty() << '\n';
}
运行输出(与 empty.output 一致):
true
false
false
false
false
true
false
true
false
逐项对照即可验证上表的语义:j_null、j_object_empty、j_array_empty 为 true;布尔、整数、浮点、双元素对象、五元素数组、非空字符串均为 false。注意 json j_null; 的默认构造即 null 值,这解释了第一行为 true。
单元测试对语义的进一步验证
tests/src/unit-capacity.cpp 中的 TEST_CASE("capacity") 对 empty() 做了系统性验证,覆盖 boolean、string、array(空/非空)、object(空/非空)、整数、无符号整数、浮点、null 八类场景。除断言各类型的返回真值外(例如空数组 j.empty() == true、非空对象 j.empty() == false),每个场景都额外验证了 C++ 标准库对"空"的定义式等价关系:
CHECK(j.empty() == (j.begin() == j.end()));
即 empty() 的结果与 begin() == end() 一致。这说明 empty() 的行为完全符合标准容器语义,可以安全地用于"是否需要跳过处理"这类判断,且对 const 与非常量对象的行为一致(测试中对 j 和 j_const 均做了断言)。
异常安全与复杂度
- 异常安全:No-throw guarantee,该函数从不抛出异常(函数签名中的
noexcept在 源码 中直接可见)。 - 时间复杂度:常数量级——前提是
array_t与object_t满足 C++ 标准 Container 概念(即其empty()为 O(1))。默认的std::vector与std::map均满足该条件。
版本历史与使用建议
empty()自 version 1.0.0 起提供。- version 3.8.0 起扩展为对 binary 类型返回
false(binary 支持本身即在该版本引入,之前的版本不存在这一分支)。
实践建议:
- 判断"对象/数组里有没有内容"时优先用
empty(),而不是size() == 0或begin() == end(),语义更清晰且与库的实现路径一致; - 判断"字符串值是否为空串"时不要用
j.empty(),应先用j.is_string()确认类型,再对取回的string_t判断长度; - 对解析后的
null值做判空时,empty()返回true,若需区分 null 与空容器,可配合is_null()、is_array()、is_object()使用。
相关接口
size():empty()定义的直接依据;array_t/object_t:决定委托行为与 O(1) 复杂度的底层容器类型;- ordered_map:用于
ordered_json的保序对象容器,同样支持 O(1) 的empty()委托。
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 StartedRust0624
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