
JSON for Modern C 无符号整数类型 number_unsigned_t 全解析存储、边界与解析降级机制【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json导读nlohmann::basic_json::number_unsigned_t是 JSON for Modern Cnlohmann/json中专门用来表示无符号整数一类 JSON 数字的 C 类型别名。本文以 API 文档 number_unsigned_t.md 为主体结合 include/nlohmann/json.hpp 与 include/nlohmann/detail/input/lexer.hpp 等源码实现讲解它如何由模板参数驱动、默认是什么类型、能存多大范围、序列化/反序列化时的边界行为以及为何它会被设计成直接内嵌存储。读完本文你将能准确理解库对无符号 JSON 整数的类型建模并能在自定义basic_json时正确调整这一类型。number_unsigned_t 是什么JSON 数字的三种 C 映射之一在 JSON for Modern C 中basic_json 类是库的核心。它的文档类型定义中明确给出了这条别名using number_unsigned_t NumberUnsignedType;即basic_json::number_unsigned_t只是类模板参数NumberUnsignedType的别名。类内部的完整定义位于 include/nlohmann/json.hpp/// brief a type for a number (integer) using number_integer_t NumberIntegerType; /// brief a type for a number (unsigned) using number_unsigned_t NumberUnsignedType; /// brief a type for a number (floating-point) using number_float_t NumberFloatType;之所以要拆成三类是因为底层协议标准RFC 8259对 JSON 数字的描述本身不区分整数与浮点数字由十进制数字、可选负号、可选小数部分和可选指数部分组成。但对 C 程序来说如果能提前知道某个数值是有符号整数、无符号整数还是浮点数就能采用精度更高的存储与计算方式。因此库与标准不同将数字细分成了三个类型number_integer_t——有符号整数number_unsigned_t——无符号整数即本文主角number_float_t——浮点数。与之配套运行时还会通过value_t枚举区分当前 JSON 值的实际类别。在 include/nlohmann/json.hpp 的存储注释中可以看到三种 number 的value_t分支分别是number_integer、number_unsigned与number_float三者各自对应一个独立类型。这保证了一个不带负号、落在无符号整数范围内的 JSON 数字在反序列化后会被保留为整数而不会退化为浮点从而保持精确性。模板参数 NumberUnsignedType 与默认类型NumberUnsignedType是basic_json类模板众多模板参数中的一个。查看前向声明 include/nlohmann/json_fwd.hpp 可以确认它在模板参数列表中的位置与默认值template templatetypename U, typename V, typename... Args class ObjectType std::map, templatetypename U, typename... Args class ArrayType std::vector, class StringType std::string, class BooleanType bool, class NumberIntegerType std::int64_t, class NumberUnsignedType std::uint64_t, // ← 本文主题 class NumberFloatType double, templatetypename U class AllocatorType std::allocator, templatetypename T, typename SFINAE void class JSONSerializer adl_serializer, class BinaryType std::vectorstd::uint8_t, class CustomBaseClass void class basic_json;NumberUnsignedType紧跟在NumberIntegerType默认std::int64_t之后、NumberFloatType默认double之前默认值为std::uint64_t。因此使用默认模板参数时nlohmann::json的number_unsigned_t即std::uint64_t。文档自带的示例 docs/mkdocs/docs/examples/number_unsigned_t.cpp 用编译期类型特征验证了这一结论#include iostream #include iomanip #include nlohmann/json.hpp using json nlohmann::json; int main() { std::cout std::boolalpha std::is_samestd::uint64_t, json::number_unsigned_t::value std::endl; }程序输出为true自定义 NumberUnsignedType 的适用前提如果你需要为无符号整数选用更窄或更宽的自定义整数类型可以通过显式指定模板参数来实例化basic_json例如把无符号类型换成std::uint32_t。但需要注意这类模板参数众多且顺序固定显式指定靠后的参数时必须为前面的参数补上默认值实践中通常借助using别名封装。库的json_fwd.hpp中给出的是完整参数列表这为自定义提供了明确依据。需要强调的是number_integer_t、number_unsigned_t、number_float_t三者会共同参与能否精确容纳该数字的判定见下文反序列化节自定义时取值范围也随之变化建议保证三者覆盖库内部对[0, UINT64_MAX]等区间的处理逻辑不被破坏。默认行为前导零的 C 与 JSON 语义差异原文档特别提醒了一个容易踩坑的默认行为差异RFC 8259 不允许数字出现前导零但 C 的整数字面量中前导零会把字面量解释为八进制数。也就是说同样的写法010在 JSON 文本里是非法的而在 C 源码里是八进制字面量。JSON for Modern C 内部一旦用 C 整数类型保存了来自 C 字面量的值就会按十进制输出真实数值。文档明确给出示例C 整数字面量010序列化后得到的是8。这个现象的本质是库只负责存储与输出的值八进制是 C 编译期的字面量语义一旦值进入std::uint64_t变量前导零信息就丢失了。反方向则严格得多——文档指出在反序列化阶段即从 JSON 文本解析出现前导零会直接报错。这一点与词法分析阶段对数字语法的校验是吻合的lexer 在scan_number()的有限状态机中严格匹配 JSON 数字文法0后紧跟数字0–9会被判定为非法输入而进入错误路径。因此C 侧的宽松八进制与 JSON 侧的严格拒绝前导零不可混用向json对象塞入 C 字面量是安全的但向解析器喂入带前导零的文本则必然失败。数值范围、溢出与互操作性可表示范围RFC 8259 允许实现对数字的范围与精度设定上限。在默认类型std::uint64_t下最大可表示的无符号整数是18446744073709551615即UINT64_MAX最小可表示的无符号整数是0。构造函数侧的溢出行为当直接使用超出范围的整数值去构造 JSON例如用一个大于UINT64_MAX的值初始化文档指出会发生overflow / underflow——这是 C 数值转换本身的语义库不会把越界的无符号整数悄悄改存成别的类型。反序列化侧的自动降级与构造函数不同从 JSON 文本解析时库会更加智能太大的正整数值不会被硬塞进number_unsigned_t而是自动改用number_integer_t或number_float_t来存储。原文档将这一机制表述为 too large or small integer numbers will automatically be stored asnumber_integer_tornumber_float_t。从当前源码实现看这一降级发生在 include/nlohmann/detail/input/lexer.hpp 的scan_number收尾阶段scan_number_done: // unget the character after the number ... char* endptr nullptr; errno 0; // try to parse integers first and fall back to floats if (number_type token_type::value_unsigned) { const auto x std::strtoull(token_buffer.data(), endptr, 10); JSON_ASSERT(endptr token_buffer.data() token_buffer.size()); if (errno ! ERANGE) { value_unsigned static_castnumber_unsigned_t(x); if (value_unsigned x) { return token_type::value_unsigned; // 精确容纳按无符号整数返回 } } } else if (number_type token_type::value_integer) { const auto x std::strtoll(token_buffer.data(), endptr, 10); JSON_ASSERT(endptr token_buffer.data() token_buffer.size()); if (errno ! ERANGE) { value_integer static_castnumber_integer_t(x); if (value_integer x) { return token_type::value_integer; // 精确容纳按有符号整数返回 } } } // this code is reached if we parse a floating-point number or if an // integer conversion above failed strtof(value_float, token_buffer.data(), endptr); ... return token_type::value_float; // 溢出/失败 → 以浮点数保留 }从这段代码可以推断出两条关键信息双重校验解析不仅检查errno ! ERANGE避免范围错误还检查value_unsigned x回转到number_unsigned_t后是否仍与原始值相等两者同时满足才会把该文本标记为无符号整数 token。这一设计保证任何按uint64_t会丢精度的情况都不会被误判。降级落点在当前版本中一旦无符号或有符号整数的strtoull/strtoll转换失败溢出或截断解析器会统一回退到strtof的浮点解析路径并返回value_float即以浮点数的形式保留该数值避免解析失败或静默截断。这也印证了文档中解析阶段数值过大不会直接报错或溢出而是自动换用更宽松的数值类型的总体行为。跨实现互操作性区间RFC 8259 同时指出若软件使用的是 IEEE-754 双精度等实现整数区间$[-2^{53}1,, 2^{53}-1]$才能保证各实现精确一致。原文档对此的结论是由于该区间与number_integer_t的可表示范围合并考虑后是[0, UINT64_MAX]的子区间因此本类的整数类型在该互操作区间内是互操作interoperable的——即参与交换的各方能对范围内的整数值达成一致不丢失精度。存储方式值语义直接内嵌文档对存储给出的结论很简洁整数值直接存放在basic_json对象内部。这一点由底层实现充分支撑。basic_json为每种 JSON 值维护了一个union json_value见 include/nlohmann/json.hpp其中number_unsigned_t是作为普通值成员而非指针存放的union json_value { object_t* object; // 变长类型以指针存放 array_t* array; string_t* string; binary_t* binary; boolean_t boolean; number_integer_t number_integer; // 定长数字值直接内嵌 number_unsigned_t number_unsigned; // ← 无符号整数直接内嵌 number_float_t number_float; ... };注意对比对象、数组、字符串、二进制这类变长类型在 union 中存的是指针堆分配后挂指针而三类 number 与 boolean 这类定长类型直接按值占用 union 空间。这也解释了为何文档注释强调使用默认值类型时 union 大小不应超过 64 位——uint64_t、int64_t、double恰好都是 8 字节配合指针型成员整体紧凑。从存储设计上可以推断数字类 JSON 值具有值语义构造、拷贝、比较时就是对uint64_t的常规操作不涉及堆分配这也是库能保证基本类型零额外动态内存开销的环节之一。当向 JSON 写入无符号整数时类型转换发生在 include/nlohmann/detail/conversions/to_json.hpp 的 ADL 序列化器/外部构造函数中例如static void construct(BasicJsonType j, typename BasicJsonType::number_unsigned_t val) noexcept其上游还会通过is_compatible_integer_type检测兼容的无符号整数类型如unsigned long、size_t等并将其static_cast到number_unsigned_t后走value_t::number_unsigned的构造路径见 to_json.hpp。换句话说任何能被无损容纳的无符号 C 整数都能安全赋给 JSON且最终统一落在number_unsigned_t上。类型相关 API 与测试佐证围绕number_unsigned_t库提供了若干类型反射与指针访问接口测试用例则落在 tests/src 下可在当前仓库中直接验证编译期类型判定tests/src/unit-constructor1.cpp 用std::get/get_ptr校验构造后的存储类型例如CHECK(std::get3(t_out) j[3].get_ptrconst json::number_unsigned_t*())以及将无符号值构造进 JSON 后仍能取回json::number_unsigned_t引用。指针访问tests/src/unit-pointer_access.cpp 分别对number_unsigned_t与const number_unsigned_t调用get_ptrT()确认只有value_t实际为无符号整数时指针才非空否则为nullptr——这是区分无符号整数 JSON 值与有符号/浮点/其它类型 JSON 值的可靠手段。类型转换tests/src/unit-conversions.cpp 验证getjson::number_unsigned_t()能从无符号整数 JSON 值中无损取出std::uint64_t。SAX 解析回调tests/src/unit-class_parser.cpp 等多个测试的 SAX 处理器都实现了bool number_unsigned(json::number_unsigned_t val)回调说明解析流程会把无符号整数 token以number_unsigned_t实参形式通知上层lexer.hpp 中对应取值的get_number_unsigned()即返回该类型的值。实际编码中的典型用法如下#include nlohmann/json.hpp #include cassert #include cstdint #include type_traits using json nlohmann::json; int main() { // 1) 无符号整数被精确保存不丢失、不转浮点 json j std::uint64_t{18446744073709551615ULL}; // UINT64_MAX assert(j.is_number_unsigned()); assert(j.getstd::uint64_t() 18446744073709551615ULL); // 2) 编译期类型检查 static_assert(std::is_samejson::number_unsigned_t, std::uint64_t::value, default number_unsigned_t must be std::uint64_t); // 3) 负数不会被塞进无符号类型而是走有符号/浮点路径 json negative -5; assert(!negative.is_number_unsigned()); }需要说明的是is_number_unsigned()这类查询接口与value_t::number_unsigned分支协同工作可用于在编写通用代码时按类别分派处理逻辑若要读取底层指针优先使用get_ptrconst json::number_unsigned_t*()并判空相关契约由 unit-pointer_access.cpp 保证。版本历史number_unsigned_t类型别名自version 2.0.0起引入此后一直作为basic_json公开的类型成员存在见 include/nlohmann/json.hpp 的注释/// sa ... /api/basic_json/number_unsigned_t/。小结number_unsigned_t是 JSON for Modern C 为无符号 JSON 整数设计的精确存储单元默认退化为std::uint64_t通过类模板参数NumberUnsignedType可整体替换它在union json_value中按值内嵌、零堆分配并以value_t::number_unsigned与number_integer、number_float严格区分解析阶段遇到超范围整数不会硬性溢出而是依据 lexer.hpp 中先整数、失败回退浮点的策略自动降级保存。理解这一类型的边界与降级链路是安全处理大整数 JSON、自定义basic_json数值类型以及排查整数值为何变成浮点/为何解析报错类问题的基础。【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考