JSON for Modern C++ 类型检查:深入解析 nlohmann::basic_json 的 is_number() 接口
导读
在 nlohmann/json(JSON for Modern C++) 中,basic_json 实例可以动态承载 null、布尔、有符号整数、无符号整数、浮点数、对象、数组、字符串与二进制等多种 JSON 值。is_number() 是这套类型检查家族中最常用的入口之一,用于判断当前值是否为「数字」——包括有符号整数、无符号整数与浮点数三种底层类型。读完本文你将掌握 is_number() 的完整语义、与 is_number_integer() / is_number_unsigned() / is_number_float() 的区分关系、底层实现原理,以及如何结合运行示例与单元测试在真实 C++ 代码中正确地用它守卫数据分支。
函数签名与语义
is_number() 的完整定义位于 include/nlohmann/json.hpp 的 basic_json 类型检查(inspection)区段:
constexpr bool is_number() const noexcept;
该函数返回 true 当且仅当 JSON 值是数字,且涵盖三类底层存储:
- 有符号整数(对应内部标签
value_t::number_integer); - 无符号整数(对应内部标签
value_t::number_unsigned); - 浮点数(对应内部标签
value_t::number_float)。
与之相对,false 意味着该值是 null、布尔、字符串、对象、数组或二进制数据等其他类型。
返回值、异常安全与复杂度
| 维度 | 结论 |
|---|---|
| 返回值 | 类型为数字时(不论有符号整数、无符号整数还是浮点)返回 true,否则返回 false |
| 异常安全 | No-throw guarantee,任何情况下都不会抛出异常 |
| 复杂度 | 常数时间 O(1),只做若干枚举比较 |
函数声明为 constexpr 且 noexcept,意味着它既可以在常量表达式上下文中使用,也可以放心地出现在不可能抛出异常的关键路径上,例如无异常(-fno-exceptions)的嵌入式环境。这一点在 include/nlohmann/json.hpp 中整个类型检查函数族(is_null、is_boolean、is_number、is_array、is_string、is_binary 等)都保持了一致的设计约定。
底层实现:一次枚举标签的复合判定
阅读 include/nlohmann/json.hpp 可以发现,is_number() 的实现本身只是一个复合谓词:
constexpr bool is_number() const noexcept
{
return is_number_integer() || is_number_float();
}
而它的两个组成部分分别是独立的枚举比较(include/nlohmann/json.hpp):
constexpr bool is_number_integer() const noexcept
{
return m_data.m_type == value_t::number_integer || m_data.m_type == value_t::number_unsigned;
}
constexpr bool is_number_unsigned() const noexcept
{
return m_data.m_type == value_t::number_unsigned;
}
constexpr bool is_number_float() const noexcept
{
return m_data.m_type == value_t::number_float;
}
可以看到,所有判定最终都归结为对 m_data.m_type(即 value_t 枚举 中的类型标签)的一次或多次等值比较,没有任何动态分配、字符串解析或遍历开销,这正是「常数复杂度 + 不抛异常」的来源。从类型覆盖面推导出的等价关系如下:
is_number()的覆盖范围 =is_number_integer()∪is_number_float();is_number_integer()的覆盖范围 =is_number_unsigned()∪(纯有符号整数);- 即
is_number_integer()蕴含is_number(),is_number_unsigned()蕴含is_number_integer()。
重要:解析器不会把带小数的字面量视为整数
需要注意一点:判定结果取决于值在构造或解析后最终落入的底层标签。例如解析 "1.0" 这样的 JSON 文本时会得到 number_float 标签,因此 is_number() 与 is_number_float() 都为 true,但 is_number_integer() 为 false。若你需要「整数值」语义(例如作为数组下标或计数),应使用 is_number_integer() 或 is_number_unsigned() 而非笼统的 is_number(),这一点在类型守卫时极易踩坑。
覆盖全部 JSON 类型的完整运行示例
仓库在 docs/mkdocs/docs/examples/is_number.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_number()
std::cout << std::boolalpha;
std::cout << j_null.is_number() << '\n';
std::cout << j_boolean.is_number() << '\n';
std::cout << j_number_integer.is_number() << '\n';
std::cout << j_number_unsigned_integer.is_number() << '\n';
std::cout << j_number_float.is_number() << '\n';
std::cout << j_object.is_number() << '\n';
std::cout << j_array.is_number() << '\n';
std::cout << j_string.is_number() << '\n';
std::cout << j_binary.is_number() << '\n';
}
对应输出记录在 docs/mkdocs/docs/examples/is_number.output:
false
false
true
true
true
false
false
false
false
逐行解读:
| 取值 | 底层标签 | is_number() |
|---|---|---|
nullptr(默认构造) |
value_t::null |
false |
true |
value_t::boolean |
false |
17 |
value_t::number_integer |
true |
12345678987654321u |
value_t::number_unsigned |
true |
23.42 |
value_t::number_float |
true |
{"one",1},{"two",2} |
value_t::object |
false |
{1,2,4,8,16} |
value_t::array |
false |
"Hello, world" |
value_t::string |
false |
json::binary({1,2,3}) |
value_t::binary |
false |
需要注意示例中无符号整数采用 12345678987654321u(超过 64 位有符号整数范围的场景)来确保字面量落入无符号分支;对同样可被有符号整数容纳的较小无符号值,构造时仍可能被归入 number_integer,判定行为由实际标签决定。
编译运行方式
将上述源文件与本仓库头文件结合编译即可,头文件采用单头分发模式,最简单的方式是使用 single_include/nlohmann/json.hpp:
g++ -std=c++11 -I single_include is_number.cpp -o is_number
./is_number
工程支持 C++11 及以上标准;若你使用 CMake,也可通过 Makefile 或仓库根目录的 CMakeLists.txt 引入,并在 target_link_libraries 中链接 nlohmann_json::nlohmann_json 目标。
单元测试视角:与全部 is_* 兄弟函数的联动验证
is_number() 的行为并非孤立存在,它与整个类型检查函数族相互印证。仓库的检查测试位于 tests/src/unit-inspection.cpp,其中的 "convenience type checker" 区段针对对象、数组、null、布尔、字符串、三类数字、二进制、discarded 等每种标签,同时断言全部 is_* 谓词的结果,确保各个标签的判定互斥且完备。
例如针对三种数字标签的核心断言(tests/src/unit-inspection.cpp):
SECTION("number (integer)")
{
json const j(42);
CHECK(j.is_number());
CHECK(j.is_number_integer());
CHECK(!j.is_number_unsigned());
CHECK(!j.is_number_float());
}
SECTION("number (unsigned)")
{
json const j(42u);
CHECK(j.is_number());
CHECK(j.is_number_integer());
CHECK(j.is_number_unsigned());
CHECK(!j.is_number_float());
}
SECTION("number (floating-point)")
{
json const j(42.23);
CHECK(j.is_number());
CHECK(!j.is_number_integer());
CHECK(!j.is_number_unsigned());
CHECK(j.is_number_float());
}
而针对对象、数组、null、布尔、字符串与二进制等非数字标签的测试段(tests/src/unit-inspection.cpp)则一致地断言 CHECK(!j.is_number()),例如 json const j {"foo", 1, 1u, 42.23, false}(数组整体)不为数字。这些测试可以在构建测试目标后运行(仓库使用 doctest 框架),是验证本文结论最直接的可执行证据。
典型实战用法
由于 JSON 文本中的数字在解析后可能落入三种标签之一,编写「数字」处理逻辑时应优先采用如下守卫模式:
if (j.is_number())
{
// 数字:可能在 number_integer / number_unsigned / number_float 中
// 若需要统一计算,可用 get<double>() 等做转换后再运算
}
else
{
// 处理 null/boolean/string/object/array/binary 等非数字分支
}
如需更细的语义区分,可以按精度需求分级:
if (j.is_number_unsigned()) // 无符号整数:保证非负
{
// 例如作为索引或计数使用
}
else if (j.is_number_integer()) // 有符号整数
{
// 例如 ID、枚举等整型字段
}
else if (j.is_number_float()) // 浮点数
{
// 例如比率、坐标等小数场景
}
else
{
// 非数字,做类型错误处理
}
值得留意的是 12345678987654321u 这类超出 64 位有符号范围、又小于 64 位无符号范围上限的字面量,若不显式加 u 后缀将无法直接放入 basic_json;这种「整数溢出保护」的构造语义使得 is_number() 家族在无符号大数场景下更加重要。
版本历史与演进
根据 API 文档的 version history:
- 1.0.0:函数随库首次发布加入;
- 2.0.0:语义扩展——在此版本之前,无符号整数需要先经转换才能判定为数字,2.0.0 起
is_number()对无符号整数同样返回true,与文档中「涵盖有符号、无符号整数及浮点」的现行语义一致。
is_number() 属于 basic_json 完整类型检查 API 的一部分,常用 API 文档页面包括:
- basic_json 类型总览
- is_number_integer():是否为有符号或无符号整数
- is_number_unsigned():是否为无符号整数
- is_number_float():是否为浮点数
若需在 C++11 及以上的任意标准、单头引入或 CMake/模块等不同分发方式下使用该 API,可以分别参考 single_include 单头文件、根目录 CMakeLists.txt 与 模块实现。
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