首页
/ JSON for Modern C++ 中 nlohmann::basic_json::empty() 的深度解析:返回值语义、源码实现与测试验证

JSON for Modern C++ 中 nlohmann::basic_json::empty() 的深度解析:返回值语义、源码实现与测试验证

2026-09-06 15:37:48作者:侯霆垣

本篇指南基于官方 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.hppcapacity(容量)功能组的开头,紧邻 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;
        }
    }
}

可以从中读出三点实现细节:

  1. basic_json 是"类型标签 + 联合数据"结构。内部成员 m_data.m_type 记录当前值属于 value_t 枚举的哪一种,m_data.m_value 中则存放对应的实际容器(array 指针、object 指针等)。empty() 的第一层判断完全由类型标签驱动。
  2. 委托而非计算。对 array/object,函数直接把判断交给 array_t/object_tempty(),因此行为与所用容器类型严格一致。若用户通过模板参数改用 ordered_map 作为 object 容器(即 ordered_json),从源码结构看 ordered_map 继承自 std::vector<std::pair<const Key, T>>(见 include/nlohmann/ordered_map.hpp),其 empty() 同样是 O(1),空对象判断结果与 std::map 版本完全一致。
  3. 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_nullj_object_emptyj_array_emptytrue;布尔、整数、浮点、双元素对象、五元素数组、非空字符串均为 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 与非常量对象的行为一致(测试中对 jj_const 均做了断言)。

异常安全与复杂度

  • 异常安全:No-throw guarantee,该函数从不抛出异常(函数签名中的 noexcept源码 中直接可见)。
  • 时间复杂度:常数量级——前提是 array_tobject_t 满足 C++ 标准 Container 概念(即其 empty() 为 O(1))。默认的 std::vectorstd::map 均满足该条件。

版本历史与使用建议

  • empty()version 1.0.0 起提供。
  • version 3.8.0 起扩展为对 binary 类型返回 false(binary 支持本身即在该版本引入,之前的版本不存在这一分支)。

实践建议:

  • 判断"对象/数组里有没有内容"时优先用 empty(),而不是 size() == 0begin() == 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() 委托。
登录后查看全文
热门项目推荐
相关项目推荐