nlohmann::basic_json::binary_t 详解:JSON for Modern C++ 中的二进制数据类型与子类型机制 nlohmann::basic_json::binary_t 详解JSON for Modern C 中的二进制数据类型与子类型机制【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/jsonbinary_t是 nlohmann::basic_json 用来承载 CBOR、MessagePack、BSON 等二进制序列化格式中出现的二进制数据的成员类型。阅读本文后你将理解它为何不属于标准 JSON、其底层容器与子类型subtype如何组织以及如何在自定义BinaryType下让容器被自动识别为二进制值从而正确完成各类二进制格式的往返序列化。类型定义为二进制格式而生的扩展类型binary_t的官方定义非常简洁它只是 byte_container_with_subtype 的别名using binary_t byte_container_with_subtypeBinaryType;该定义位于 basic_json 主头文件/// brief a type for a packed binary type using binary_t nlohmann::byte_container_with_subtypeBinaryType;文档明确指出这一类型不是标准 JSON 的一部分它的存在纯粹是为了与二进制序列化格式保持兼容。三类典型来源分别是CBORRFC 8949Major Type 2即字节串byte string字符串的字节数等于参数值MessagePackbin 格式族在字节数组本体之外使用 2、3 或 5 字节的额外控制信息存储长度BSON规定了多种二进制子类型其中binary_t对应的是Generic binary subtype——最常用的二进制子类型应作为驱动与工具的默认值。这些规范对内部表示的唯一要求是存储单元必须是某种可以被拆解为字节的数组。模板参数 BinaryType 的约束binary_t携带一个模板参数BinaryType即实际存储字节序列的容器类型。它虽然没有被正式表述为 C concept但从 byte_container_with_subtype 的实现可以看出其硬性要求要求原因可默认构造、可拷贝/移动构造byte_container_with_subtype直接公有继承自BinaryType支持push_back()、.data()、.size()序列化器需要读取原始字节与长度value_type必须恰好是 1 字节宽如std::uint8_t、char、std::byte各二进制序列化器通过reinterpret_cast读写容器的原始字节仅对字节级元素才正确std::vectorstd::intptr_t这类容器不能用作BinaryTypevalue_type恰好 1 字节的这一点可以在 binary_writer.hpp 中得到印证MessagePack 的写出代码直接执行oa-write_characters( reinterpret_castconst CharType*(j.m_data.m_value.binary-data()), N);reinterpret_cast将容器首地址直接重解释为字节缓冲这正是元素必须恰好一个字节约束的来源。默认类型与存储方式默认值BinaryType默认为std::vectorstd::uint8_t这是现代 C 中表达字节数组最常见的方式。存储方式二进制值在basic_json内部以指针形式存储。对二进制值的任何访问都要先解引用binary_t*指针源码中表现为m_data.m_value.binary如json_value联合体中的构造入口 json.hpp#L576-L585通过createbinary_t(...)分配堆内存。子类型以 std::uint64_t 承载的元数据作为实现细节二进制数据的子类型subtype以一个std::uint64_t随数据一起携带。文档特别强调它兼容两种使用二进制子类型的格式但两种格式的具体编号方案互不兼容在两者之间转换是用户自己的责任。从 byte_container_with_subtype.hpp 源码可见其完整实现private: subtype_type m_subtype 0; // subtype_type 即 std::uint64_t bool m_has_subtype false;对外暴露的四个操作方法行为set_subtype(subtype_type)设置子类型并标记m_has_subtype truesubtype()返回子类型若未设置返回static_castsubtype_type(-1)has_subtype()判断当前值是否携带子类型clear_subtype()将子类型清零并复位m_has_subtype值得注意的细节是operator的比较语义它不仅比较底层字节序列还同时比较m_subtype与m_has_subtype见 byte_container_with_subtype.hpp#L57-L61。也就是说字节相同但子类型不同的两个二进制值并不相等这是做断言测试时必须留意的点。各序列化格式对子类型的编码规则文档中Notes on subtypes一节给出的三种格式行为都能在 binary_writer.hpp 中找到对应实现CBOR二进制值表示为字节串子类型以 tag 的形式写出。源码中 binary_writer.hpp#L310-L330 会先检查has_subtype()再按子类型数值的大小选择 8/16/32/64 位宽度写出对应 tag。MessagePack设置了子类型且长度为 1、2、4、8、16使用 fixext 家族fixext1/2/4/8/16对应控制字节0xD4–0xD8设置了子类型但长度不在上述集合使用 ext 家族ext8/ext16/ext32对应0xC7/0xC8/0xC9子类型以有符号 8 位整数追加写出未设置子类型使用 bin 家族bin8/bin16/bin320xC4/0xC5/0xC6。这段逻辑完整体现在 binary_writer.hpp#L619-L692// step 0: determine if the binary type has a set subtype to // determine whether to use the ext or fixext types const bool use_ext j.m_data.m_value.binary-has_subtype(); ... if (use_ext) { switch (N) { case 1: output_type 0xD4; // fixext 1 case 2: output_type 0xD5; // fixext 2 case 4: output_type 0xD6; // fixext 4 case 8: output_type 0xD7; // fixext 8 case 16: output_type 0xD8; // fixext 16 default: output_type 0xC7; // ext 8 } }BSON设置了子类型直接使用它作为无符号 8 位整数写出未设置子类型使用通用二进制子类型0x00。对应实现见 binary_writer.hpp#L1190write_number(value.has_subtype() ? static_caststd::uint8_t(value.subtype()) : static_caststd::uint8_t(0x00));显式创建二进制值json::binary() 工厂函数由于默认的std::vectorstd::uint8_t会被解释为数组要把一个字节容器显式声明为二进制值应使用静态工厂函数binary()。json.hpp#L1005-L1047 提供四个重载// 无子类型const 引用 / 右值 static basic_json binary(const typename binary_t::container_type init); static basic_json binary(typename binary_t::container_type init); // 带子类型 static basic_json binary(const typename binary_t::container_type init, typename binary_t::subtype_type subtype); static basic_json binary(typename binary_t::container_type init, typename binary_t::subtype_type subtype);实现上这四个函数都会把值类型标记为value_t::binary带子类型的版本则构造binary_t(init, subtype)使m_has_subtype置位。配合后文介绍的方法典型用法为std::vectorstd::uint8_t bytes{1, 2, 3}; json j json::binary(bytes, 0x01); // 显式创建带子类型的二进制值 assert(j.is_binary());自定义 BinaryType容器自动识别为二进制值文档Custom BinaryType behavior一节给出了一个重要的便利特性当配置了非默认的自定义BinaryType时把该类型的值直接赋给basic_json实例会自动被识别为二进制值而不是数组。官方给出的完整示例如下using custom_json nlohmann::basic_json nlohmann::ordered_map, // ObjectType std::vector, // ArrayType std::string, // StringType bool, // BooleanType std::int64_t, // NumberIntegerType std::uint64_t, // NumberUnsignedType double, // NumberFloatType std::allocator, // AllocatorType nlohmann::adl_serializer, std::vectorstd::byte // Custom BinaryType ; std::vectorstd::byte data{std::byte{1}, std::byte{2}, std::byte{3}}; custom_json j data; // Creates a binary value, not an array assert(j.is_binary()); // Round-tripping works seamlessly auto extracted j.getstd::vectorstd::byte(); assert(extracted data);从源码结构看这一自动识别机制的依据在 type_traits.hpp#L642-L643只有当传入容器类型与binary_t::container_type完全相同、且该容器类型不是默认的std::vectorstd::uint8_t时类型特征才将其判定为可转换为二进制值。文档同时强调了边界默认配置nlohmann::json中std::vectorstd::uint8_t依旧被当作数组处理这是出于向后兼容的考虑自动识别特性仅对自定义非默认BinaryType生效。官方示例验证 binary_t 的默认别名examples/binary_t.cpp 演示了默认情况下binary_t就是byte_container_with_subtypestd::vectorstd::uint8_t这一事实#include iostream #include iomanip #include nlohmann/json.hpp using json nlohmann::json; int main() { std::cout std::boolalpha std::is_samenlohmann::byte_container_with_subtypestd::vectorstd::uint8_t, json::binary_t::value std::endl; }运行输出见 binary_t.outputtrue这说明默认的json::binary_t与手工写出的byte_container_with_subtypestd::vectorstd::uint8_t是同一个类型。相关接口与版本历史进一步探索二进制值相关的 API可参考byte_container_with_subtype 文档set_subtype、subtype、has_subtype、clear_subtype的完整说明get_binary 文档以binary_t形式直接访问底层容器的安全入口类型不符时会抛出异常。版本历史binary_t自3.8.0版本引入子类型在3.10.0起由原有类型改为std::uint64_t。【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考