JSON for Modern C++ 类型体系解析:nlohmann::basic_json::value_t 从定义、比较规则到实战
value_t 是 nlohmann/json(JSON for Modern C++)内部用来统一标识所有 JSON 值类型的基础枚举,它是 type() 查询函数与全部 is_* 类型检查函数的判定依据。本文以官方 API 文档 docs/mkdocs/docs/api/basic_json/value_t.md 为主线,结合 include/nlohmann/detail/value_t.hpp 等源码实现,完整讲解枚举定义、类型顺序与比较运算符重载、数字三分类的设计动机,并给出可直接运行的类型查询示例,帮助你在处理 JSON 值时准确判断其类型并正确使用比较语义。
value_t 枚举定义与 10 个类型标识符
官方文档给出的完整定义如下:
enum class value_t : std::uint8_t {
null,
object,
array,
string,
boolean,
number_integer,
number_unsigned,
number_float,
binary,
discarded
};
该枚举在源码中定义于 include/nlohmann/detail/value_t.hpp,底层宽度为 std::uint8_t。每个枚举项的含义可由源码注释直接确认:
| 枚举值 | 含义 |
|---|---|
null |
null 值 |
object |
对象(无序的键值对集合) |
array |
数组(有序的值集合) |
string |
字符串值 |
boolean |
布尔值 |
number_integer |
数字(有符号整数) |
number_unsigned |
数字(无符号整数) |
number_float |
数字(浮点数) |
binary |
二进制数组(有序字节集合) |
discarded |
被解析器回调函数丢弃的值 |
value_t 是整个库区分子类型(distinguish the stored values)的内部基础:basic_json 的所有类型检查函数都建立在这一枚举之上。从源码结构看,basic_json 实例内部持有类型字段,type() 直接返回它:
// include/nlohmann/json.hpp
constexpr value_t type() const noexcept
{
return m_data.m_type;
}
见 include/nlohmann/json.hpp#L1360-L1363。
依赖 value_t 的类型检查函数族
文档明确指出,以下函数均依赖 value_t 进行判定:
is_nullis_object、is_arrayis_string、is_booleanis_number(含is_number_integer、is_number_unsigned、is_number_float)is_discarded、is_binaryis_primitive、is_structured
它们在源码中的实现模式高度一致,即直接比对 m_data.m_type 与对应枚举值,例如 include/nlohmann/json.hpp#L1381-L1384:
constexpr bool is_null() const noexcept
{
return m_data.m_type == value_t::null;
}
而两个分类函数 is_primitive() 与 is_structured() 则是按类型集合划分的快捷判断,见 include/nlohmann/json.hpp#L1365-L1377:
/// 原始类型:null、string、boolean、number、binary
constexpr bool is_primitive() const noexcept
{
return is_null() || is_string() || is_boolean() || is_number() || is_binary();
}
/// 结构化类型:array、object
constexpr bool is_structured() const noexcept
{
return is_array() || is_object();
}
值得注意的是 discarded:它表示被解析器回调函数(parser callback)丢弃的值。从源码结构可以印证,各 sax_parse 实现中,当回调拒绝某个元素时,库会用 basic_json(value_t::discarded) 构造占位结果(如 single_include/nlohmann/json.hpp#L25831),因此处理大型 JSON 流式解析时,is_discarded() 是检查“某部分是否被回调丢弃”的手段。
数字为何分为三种枚举值
文档特别指出,数字有三个枚举项,目的是区分不同的数字底层类型,对应的类型别名为:
number_unsigned_t:无符号整数number_integer_t:有符号整数number_float_t:浮点数,或者用于近似表示超出各自类型取值范围限制的整数
这一设计保证了整数精确性:只要整数在范围内,nlohmann::json 就按 number_integer / number_unsigned 存储而不转成浮点;只有真正的浮点字面量或无法容纳的整数才落入 number_float。这也是文档示例中 -17、42u、23.42 三个值能分别命中三种类型的原因。
类型顺序(Ordering)与比较运算符重载
文档中的 Ordering 说明给出了类型比较的既定顺序:
nullbooleannumber_integer、number_unsigned、number_floatobjectarraystringbinary
其中 discarded 不参与任何排序(unordered)。这个顺序在源码中通过一张查找表实现,见 include/nlohmann/detail/value_t.hpp#L86-L104:
static constexpr std::array<std::uint8_t, 9> order = {{
0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */,
1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */,
6 /* binary */
}
};
注意数组下标即枚举的整数值,而 discarded(下标 9)不在数组范围内,因此比较结果直接为 unordered / false。源码注释还说明了该顺序的设计参考:与 Python 的类型序一致(null < boolean < number < object < array < string < binary),并且刻意不将 binary 与 string 直接可比,避免 JSON 文件中出现令人意外行为。
比较运算符的重载形式由 JSON_HAS_THREE_WAY_COMPARISON 宏决定:C++20 及以上编译时提供 operator<=>,否则提供 operator<,见 include/nlohmann/detail/value_t.hpp#L80-L104。
文档给出的比较运算符警告同样值得重视:operator< 和 C++20 起的 operator<=> 按上述“类型序”比较,而其余关系/相等运算符(如 >、== 的整数序语义)在 C++20 前按各枚举项的整数值比较;由于 C++20 中各编译器对 operator<=> 生成的“rewritten candidates”处理不一致,文档建议:
- 想按类型序比较:使用
operator<或operator<=> - 想按枚举整数值比较:使用
operator==或operator!=
源码中还有一段针对 GCC 的显式 workaround(见 include/nlohmann/detail/value_t.hpp#L106-L115),注释指出 GCC 在存在用户定义 spaceship 运算符时会优先选择内建 operator< 而非重写候选(对应 GCC bug #105200),因此额外提供了 inline bool operator<(const value_t, const value_t),内部通过 std::is_lt(lhs <=> rhs) 保证行为一致。这段实现细节正是文档中“编译器行为不一致”警告的落地证据。
按类型构造 JSON 值
value_t 文档还交叉引用了构造函数 basic_json(const value_t value_type)——“创建具有给定类型默认值的 JSON 值”。该构造函数实现见 single_include/nlohmann/json.hpp#L22200-L22206:
/// create an empty value with a given type
basic_json(const value_t v)
: m_data(v)
{
assert_invariant();
}
这意味着可以显式声明一个“已知类型但内容为空/默认”的 JSON 值,例如 nlohmann::json j = nlohmann::json::value_t::array; 得到一个空数组类型的值。同样的模式也用于 nullptr 构造(basic_json(std::nullptr_t) 内部调用 basic_json(value_t::null),见 single_include/nlohmann/json.hpp#L22210-L22214)。
完整示例:用 type() 查询所有 JSON 类型
官方文档的示例代码位于 docs/mkdocs/docs/examples/type.cpp,它演示了 type() 如何为每种 JSON 类型返回对应的 value_t:
#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 = 42u;
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 type()
std::cout << std::boolalpha;
std::cout << (j_null.type() == json::value_t::null) << '\n';
std::cout << (j_boolean.type() == json::value_t::boolean) << '\n';
std::cout << (j_number_integer.type() == json::value_t::number_integer) << '\n';
std::cout << (j_number_unsigned.type() == json::value_t::number_unsigned) << '\n';
std::cout << (j_number_float.type() == json::value_t::number_float) << '\n';
std::cout << (j_object.type() == json::value_t::object) << '\n';
std::cout << (j_array.type() == json::value_t::array) << '\n';
std::cout << (j_string.type() == json::value_t::string) << '\n';
}
预期输出(来自 docs/mkdocs/docs/examples/type.output)为 8 行 true,分别验证了 null、boolean、number_integer、number_unsigned、number_float、object、array、string 八种类型标识的判定均命中。
从该示例可以注意到两个实践要点:
json j_null;默认构造即得到value_t::null类型值;- 字面量
42u因带u后缀走无符号整型路径,故命中number_unsigned而非number_integer,与前述数字三分类设计相互印证。
版本历史
按官方文档记载,value_t 的演进如下:
- version 1.0.0 起提供该枚举;
- version 2.0.0 新增无符号整数类型
number_unsigned; - version 3.8.0 新增二进制类型
binary。
结合本仓库源码(文件头标注 version 3.12.0)可以看到当前实现已包含全部 10 个枚举项及 C++20 三路比较支持。
小结
value_t 虽只是一个 10 项的小枚举,却是理解 nlohmann/json 类型系统的关键枢纽:type() 读取它,is_* 函数比对它,数字三分类依赖它保证整数精度,discarded 通过它支撑流式解析丢弃语义,而 operator< / operator<=> 的自定义顺序则为不同 JSON 值之间的“类型级”比较提供了可移植语义。实际开发中,建议对任何来源不明的 JSON 值先做 is_* 类型检查再取值,并按文档建议显式选择比较运算符,以避免 C++20 重写候选带来的行为差异。
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 StartedRust0627
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