JSON for Modern C++ 类型检查系列:深入解析 basic_json::is_array() 判定数组类型

JSON for Modern C++ 类型检查系列:深入解析 basic_json::is_array() 判定数组类型

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

本文围绕 JSON for Modern C++(nlohmann/json 仓库)中 nlohmann::basic_json::is_array() 成员函数展开,从函数签名、返回语义、底层实现到典型实战场景,逐层剖析「如何判断一个 JSON 值是否为数组」。读完本文,你将理解数组类型在 basic_json 内部的表示方式、is_array() 与兄弟类型检查函数(is_object、is_primitive、is_structured 等)的分工协作,并掌握在解析数据、序列化与自定义类型转换代码中安全使用类型分派的最佳实践。

函数签名与语义

is_array() 是 basic_json 公开 API 中「对象检查(object inspection)」类型检查函数家族的成员,定义如下:

constexpr bool is_array() const noexcept;

该函数返回 #!cpp true 当且仅当 JSON 值是一个数组(array),其余所有类型一律返回 false。它不修改对象状态,也不触发任何动态分配,因此非常适合在分支判断、模板分派乃至编译期上下文(constexpr)中使用。

返回值、异常安全与复杂度

项目说明
返回值JSON 值为数组时返回 true,否则返回 false
异常安全强保证中的「不抛异常」:noexcept,函数体永不抛出异常
复杂度常数时间 O(1),仅执行一次类型标签比较

由于函数被声明为 constexpr 且不抛异常,它可以在 static_assert、编译期求值以及无异常构建(如禁用 C++ 异常的环境)中使用,不会引入运行时开销。

从源码看实现原理

is_array() 的实现位于 include/nlohmann/json.hpp,完整源码如下:

/// @brief return whether value is an array
/// @sa https://json.nlohmann.me/api/basic_json/is_array/
constexpr bool is_array() const noexcept
{
    return m_data.m_type == value_t::array;
}

要理解这行比较的含义,需要了解 JSON 值的类型标签体系:

  1. 类型枚举:库使用 detail::value_t 枚举记录值的动态类型,定义在 include/nlohmann/detail/value_t.hpp,其中与数组直接相关的是 array,其注释为「array (ordered collection of values)」,即值的有序集合(区别于 object 的「键值对无序集合」)。

  2. 公开别名:basic_json 将内部枚举公开为成员类型别名,见 include/nlohmann/json.hpp 中 using value_t = detail::value_t;,因此用户代码可以写 json::value_t::array。

  3. 类型标记存储:每个 basic_json 对象在 m_data.m_type 字段(其类型即 value_t)中维护自身当前的动态类型标签。is_array() 本质就是一次单条比较:m_data.m_type == value_t::array。

  4. 底层容器:数组元素存放在指向 array_t 的指针中,而 array_t 是模板参数 ArrayType 的实例化:using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;(见 include/nlohmann/json.hpp)。默认情况下 ArrayType 是 std::vector(见 include/nlohmann/json_fwd.hpp 的前置声明),也就是标准库 std::vector<basic_json>。

此外,value_t 枚举头还定义了跨类型比较的排序(null < boolean < number < object < array < string < binary),该排序与判断函数相互独立,但同属类型系统设计的一部分。

与兄弟判断函数的协同

is_array() 并非孤立存在,basic_json 为每种 JSON 类型都提供了对应的 is_xxx() 判断函数,全部集中在「object inspection」代码区(include/nlohmann/json.hpp):

  • is_null()、is_boolean()、is_string()、is_binary() 分别判断 null、布尔、字符串、二进制数组;
  • is_number()、is_number_integer()、is_number_unsigned()、is_number_float() 判断数值的三种细分形态;
  • is_object() 与 is_array() 判断两种复合(结构化)类型;
  • is_primitive() 与 is_structured() 是更高层的聚合判断。

其中两个高层函数直接复用了 is_array():

constexpr bool is_primitive() const noexcept
{
    return is_null() || is_string() || is_boolean() || is_number() || is_binary();
}

constexpr bool is_structured() const noexcept
{
    return is_array() || is_object();
}

这意味着 is_array() 返回 true 时,is_structured() 必然返回 true,而 is_primitive() 必然返回 false;反之亦然。相关源码见 include/nlohmann/json.hpp。与之等价的一种写法是通过公开的 type() 函数比较:

// 以下两者语义等价
if (j.is_array()) { /* ... */ }
if (j.type() == json::value_t::array) { /* ... */ }

其中 type() 返回 value_t 枚举值,详见 type() 文档。在存在大量 switch 分派的场景下用 type() 更清晰,而在简单布尔判断场景下直接调用 is_array() 更直观。

覆盖全部 JSON 类型的完整示例

原文档提供了一个贯穿全部类型的最小示例,源码位于 docs/mkdocs/docs/examples/is_array.cpp,其可运行版本如下:

#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_unsigned_integer = 12345678987654321u;
    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";
    json j_binary = json::binary({1, 2, 3});

    // call is_array()
    std::cout << std::boolalpha;
    std::cout << j_null.is_array() << '\n';
    std::cout << j_boolean.is_array() << '\n';
    std::cout << j_number_integer.is_array() << '\n';
    std::cout << j_number_unsigned_integer.is_array() << '\n';
    std::cout << j_number_float.is_array() << '\n';
    std::cout << j_object.is_array() << '\n';
    std::cout << j_array.is_array() << '\n';
    std::cout << j_string.is_array() << '\n';
    std::cout << j_binary.is_array() << '\n';
}

运行后输出(见 docs/mkdocs/docs/examples/is_array.output):

false
false
false
false
false
false
true
false
false

对照每种输入值的判定结果如下:

JSON 值类型is_array() 结果
j_null(默认构造)nullfalse
j_boolean = truebooleanfalse
j_number_integer = 17number_integerfalse
j_number_unsigned_integer = 12345678987654321unumber_unsignedfalse
j_number_float = 23.42number_floatfalse
j_object = {{"one",1},{"two",2}}objectfalse
j_array = {1,2,4,8,16}arraytrue
j_string = "Hello, world"stringfalse
j_binary = json::binary({1,2,3})binaryfalse

需要特别留意两点边界语义:

  • 二进制数据:即使底层用字节数组(std::vector<std::uint8_t>)承载,json::binary({...}) 构造出的值在类型系统上属于 value_t::binary 而非 value_t::array,因此 is_array() 返回 false。
  • 空数组:json::array() 工厂函数或 {} 初始化构造的空数组在类型标签上依然是 value_t::array,因此 is_array() 返回 true(这与 empty() 判空是两回事,见下文)。

实战要点:数组的创建与类型检查配合

明确用 json::array() 消除初始化歧义

basic_json 的初始化列表构造会根据内容自动推断类型:当列表中的每个元素都是「长度为 2、且首元素为字符串」的数组时,会被自动推断为 object,否则推断为 array(推断逻辑见 include/nlohmann/json.hpp 的初始化列表构造函数)。因此数组元素恰好是 {"key", value} 形态时容易产生歧义,应使用显式的静态工厂 json::array() 强制生成数组。

include/nlohmann/json.hpp 中 array() 的实现为:

/// @brief explicitly create an array from an initializer list
static basic_json array(initializer_list_t init = {})
{
    return basic_json(init, false, value_t::array);
}

即通过第三个参数 manual_type = value_t::array 关闭类型推导,强制按数组构造。仓库测试 tests/src/unit-readme.cpp 对这一组歧义场景做了断言覆盖:

// ways to express the empty array []
json const empty_array_implicit = {{}};
CHECK(empty_array_implicit.is_array());
json const empty_array_explicit = json::array();
CHECK(empty_array_explicit.is_array());

// a way to express an _array_ of key/value pairs [["currency", "USD"], ["value", 42.99]]
json array_not_object = json::array({ {"currency", "USD"}, {"value", 42.99} });
CHECK(array_not_object.is_array());
CHECK(array_not_object.size() == 2);
CHECK(array_not_object[0].is_array());
CHECK(array_not_object[1].is_array());

注意 {{}} 这样的写法会生成一个空数组(这是 README 中记录的惯用法,对应 JSON 文本 []),若想要空对象应使用 json::object()。

解析结果的类型守卫

在反序列化任意输入(json::parse、流解析、_json 字面量等)时,输入根节点可能是任意类型。调用方通常先用 is_array()(常与 is_object() 组合)守卫,再安全地使用 size()、at()、迭代器或下标访问。仓库中大量测试采用了这一组合,例如:

CHECK((parsed.is_object() || parsed.is_array()));
if (value.is_array())
{
    // 逐元素递归处理
}

这种「先 is_array()/is_object() 判断、再访问」的写法同样适用于 dump() 序列化前的类型检查、JSON Schema 式校验以及自定义 from_json/to_json 类型转换中对容器类型的判别。

对全部类型的正交断言(单元测试视角)

仓库的单元测试 tests/src/unit-inspection.cpp 在「convenience type checker」一节中,对 object、array、null、boolean、string、integer、unsigned、floating-point、binary、discarded 等全部类型逐一断言:每种类型都满足「自己的 is_xxx() 为真、其余全部为假」的正交性。例如对 array 类型:

SECTION("array")
{
    json const j {"foo", 1, 1u, 42.23, false};
    CHECK(!j.is_null());
    CHECK(!j.is_boolean());
    CHECK(!j.is_number());
    // ...
    CHECK(!j.is_object());
    CHECK(j.is_array());      // 数组:is_array 为 true
    CHECK(!j.is_string());
    // ...
    CHECK(!j.is_primitive()); // 数组不是原始类型
    CHECK(j.is_structured()); // 数组是结构化类型
}

组合使用建议:is_array 与 empty/size

is_array() 只回答「是不是数组」,不回答「数组是否为空」。实际使用中常将其与容器接口组合:

json const data = json::parse(input);

if (data.is_array())
{
    if (data.empty())
    {
        // 空数组 []
    }
    else
    {
        // 遍历元素:for (auto const& item : data) { ... }
    }
}
else
{
    // 非数组:按其他类型或错误路径处理
}

补充说明与版本信息

  • 本文引用的实现位于多文件头 include/nlohmann/json.hpp 与前置声明 include/nlohmann/json_fwd.hpp;实际使用中亦可直接包含仓库提供的单头文件合并且自包含版本 single_include/nlohmann/json.hpp,无需额外依赖即可编译运行示例。
  • is_array() 属于 constexpr 类型检查族,对自定义容器(如将 ArrayType 替换为其他模板)依旧生效,因为判定仅依赖类型标签 value_t::array,与具体存储容器无关;ordered_json(基于 ordered_map 的类型别名)同样适用于本函数语义。
  • 版本历史:is_array() 自 version 1.0.0 起加入该 API,长期保持签名稳定。

相关文档索引

若需进一步了解类型系统与相邻判断接口,可在本仓库文档中继续阅读:

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

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

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值