格式)
nlohmann-json 的 to_ubjson 详解将 JSON 值序列化为 UBJSONUniversal Binary JSON格式【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/jsonJSON 文本语义直观但体积偏大、解析成本偏高。JSON for Modern Cnlohmann-json在basic_json上提供了静态成员函数to_ubjson可以把任意 JSON 值一次性转换为 UBJSONUniversal Binary JSON字节流也可写入任意的输出适配器output adapter为嵌入式传输、网络通信等对体积与解析效率敏感的场景提供了 JSON 之外的二进制交换格式。读完本文你将掌握to_ubjson的全部重载签名、use_size/use_type两个优化参数的真正含义与约束、JSON↔UBJSON 的类型映射规则以及如何结合from_ubjson构建完整的编解码闭环。一、函数签名与三种调用形态to_ubjson是basic_json的静态成员函数官方 API 定义见 docs/mkdocs/docs/api/basic_json/to_ubjson.md// (1) 返回字节向量 static std::vectorstd::uint8_t to_ubjson(const basic_json j, const bool use_size false, const bool use_type false); // (2) 写入 uint8_t 输出适配器 static void to_ubjson(const basic_json j, detail::output_adapterstd::uint8_t o, const bool use_size false, const bool use_type false); // (3) 写入 char 输出适配器 static void to_ubjson(const basic_json j, detail::output_adapterchar o, const bool use_size false, const bool use_type false);三种形态的功能关系很清晰形态 (1)序列化后直接返回std::vectorstd::uint8_t是最常用的入口。从 include/nlohmann/json.hpp 的源码看它内部先构造一个空result向量再调用形态 (2) 完成写入最后返回该向量——也就是说形态 (1) 是形态 (2) 的便捷封装形态 (2)/(3)把序列化结果写到detail::output_adapterstd::uint8_t或detail::output_adapterchar适配器上不产生返回值。这两者最终都委托给底层二进制写出器static void to_ubjson(const basic_json j, detail::output_adapterstd::uint8_t o, const bool use_size false, const bool use_type false) { binary_writerstd::uint8_t(o).write_ubjson(j, use_size, use_type); }上述实现位于 include/nlohmann/json.hpp。可见真正的编码逻辑全部集中在detail::binary_writer的write_ubjsoninclude/nlohmann/detail/output/binary_writer.hpp对外暴露的三层重载只是为了让调用方既可以用“开箱即取的字节向量”也可以把数据流式地接入自有的输出容器。二、参数说明use_size 与 use_type三个参数均作用于容器类型数组与对象的编码方式参数含义默认值j待序列化的 JSON 值in只读—o承载序列化结果的输出适配器in—use_size是否为容器类型添加元素个数标注size annotationfalseuse_type是否为容器类型添加类型标注type annotation必须与use_size true组合使用falseuse_size为容器前置元素计数当use_size true时写出器会在容器起始位置先写入一个#标记及其后跟的元素数量用尽量短的长度前缀编码并省略容器结尾的闭合标记。这样接收方读完首个元素计数后就能立即得知容器大小、提前分配内存而无须扫描到末尾的]或}。需要特别留意的是根据 docs/mkdocs/docs/features/binary_formats/ubjson.md 的说明单独使用use_size true有时反而会让体积变大——计数前缀本身要占用额外字节。它的价值不在压缩而在让接收方立刻获知容器元素数量。use_type为容器折叠同构元素的类型前缀当use_size与use_type同时为true时写出器会进一步检查容器内所有元素是否属于同一类型若全部同型则在容器开头追加一个$标记与唯一的类型标记之后每个元素都不再重复携带自己的类型前缀只写裸数据从而大幅压缩字节数若元素类型不一致则退化为仅使用元素计数标注的编码。这是 UBJSON 的“Optimized Format”核心机制。若把use_type置为true而use_size仍为false则直接违反约束并触发异常见下文的“异常”小节。从编码函数看优化逻辑反映在 include/nlohmann/detail/output/binary_writer.hpp 的注释与实现上参数use_count对应#前缀即文档层面的use_size与use_type对应$前缀贯穿容器元素的递归写出过程直到到达可整体折叠的叶子元素见 write_ubjson 中数组处理。此外该函数还通过use_bjdata与bjdata_version两个内部开关复用同一套编码器支持 BJData 输出to_bjdata即调用write_ubjson(j, use_size, use_type, true, true, version)见 include/nlohmann/json.hpp这说明 UBJSON 编码器是整个“UBJSON/BJData 家族”的公共底座。三、返回值与复杂度返回值形态 (1) 返回std::vectorstd::uint8_t即携带 UBJSON 序列化结果的字节向量形态 (2)/(3) 返回空数据写入输出适配器。复杂度与 JSON 值j的大小呈线性关系Linear in the size of the JSON value。递归遍历每个值恰好一次无额外重扫描。四、异常安全与异常异常安全提供强保证strong guarantee——若中途抛出异常JSON 值j不会发生任何改变。编码过程只读取源值并写入独立的目标缓冲区不会就地修改输入。异常当use_type为true而use_size为false时抛出json::other_error错误码为502具体消息为[json.exception.other_error.502] use_type requires use_size true关于该异常类型在异常继承体系中的位置、以及other_error.501JSON Patch 操作失败与other_error.502use_type 约束的区分可查阅 docs/mkdocs/docs/home/exceptions.md。五、完整可运行示例与输出逐字节解析官方示例 docs/mkdocs/docs/examples/to_ubjson.cpp 同时演示了三种编码形态。为了便于阅读它定义了一个print_byte可打印 ASCII 字符32 byte 128按字符输出其余字节按十进制整数输出。#include iostream #include iomanip #include nlohmann/json.hpp using json nlohmann::json; using namespace nlohmann::literals; // function to print UBJSONs diagnostic format void print_byte(uint8_t byte) { if (32 byte and byte 128) { std::cout (char)byte; } else { std::cout (int)byte; } } int main() { // create a JSON value json j R({compact: true, schema: false})_json; // serialize it to UBJSON std::vectorstd::uint8_t v json::to_ubjson(j); // print the vector content for (auto byte : v) { print_byte(byte); } std::cout std::endl; // create an array of numbers json array {1, 2, 3, 4, 5, 6, 7, 8}; // serialize it to UBJSON using default representation std::vectorstd::uint8_t v_array json::to_ubjson(array); // serialize it to UBJSON using size optimization std::vectorstd::uint8_t v_array_size json::to_ubjson(array, true); // serialize it to UBJSON using type optimization std::vectorstd::uint8_t v_array_size_and_type json::to_ubjson(array, true, true); // print the vector contents for (auto byte : v_array) { print_byte(byte); } std::cout std::endl; for (auto byte : v_array_size) { print_byte(byte); } std::cout std::endl; for (auto byte : v_array_size_and_type) { print_byte(byte); } std::cout std::endl; }程序输出见 docs/mkdocs/docs/examples/to_ubjson.output{i7compactTi6schemaF} [i1i2i3i4i5i6i7i8] [#i8i1i2i3i4i5i6i7i8 [$i#i812345678下面逐行解析这四个结果的字节语义理解它等于理解了三个参数的差别对象默认编码{i7compactTi6schemaF}{是对象map起始标记随后对象键以“长度前缀 键名”写入i7compact表示长度 7i为 int8 长度标记、7 为长度值的字符串compact紧接着T是true标记i6schema同理是键schemaF是false标记最后的}是对象闭合标记。整个过程未开启任何优化。数组默认编码[i1i2i3i4i5i6i7i8][为数组起始标记每个元素各自携带iint8类型前缀后再写数值 1…8末尾以]闭合。每个元素要付出 1 字节前缀成本。开启 use_size[#i8i1i2i3i4i5i6i7i8数组起始后先写#count 标记与i8元素个数 8随后逐个写出带前缀的元素且省略了结尾的]。接收方凭i8即可预先得知数组长度。同时开启 use_size use_type[$i#i812345678[之后出现$i$是 type 标记i说明后续元素全部为 int8 类型再写#i8告知个数为 8之后 8 个元素不再携带各自类型前缀直接写裸字节因此输出中只剩可打印的12345678。这是四种输出中最紧凑的一种。类似的优化行为在测试套件中也被大量覆盖例如 tests/src/unit-ubjson.cpp 中针对to_ubjson(j)、to_ubjson(j, true)、to_ubjson(j, true, true)三种调用分别断言其字节序列可当作更严格的回归参考。六、JSON → UBJSON 的类型映射规则按照 UBJSON 规范库在序列化时依据JSON 值的类型与取值区间来选择最紧凑的 UBJSON 类型与标记。完整映射见 docs/mkdocs/docs/features/binary_formats/ubjson.md摘录如下JSON 值类型值 / 取值范围UBJSON 类型标记nullnullnullZbooleantruetrueTbooleanfalsefalseFnumber_integer-9223372036854775808..-2147483649int64Lnumber_integer-2147483648..-32769int32lnumber_integer-32768..-129int16Inumber_integer-128..127int8inumber_integer128..255uint8Unumber_integer256..32767int16Inumber_integer32768..2147483647int32lnumber_integer2147483648..9223372036854775807int64Lnumber_unsigned0..127int8inumber_unsigned128..255uint8Unumber_unsigned256..32767int16Inumber_unsigned32768..2147483647int32lnumber_unsigned2147483648..9223372036854775807int64Lnumber_unsigned2147483649..18446744073709551615high-precisionHnumber_float任意值float64Dstring使用最短长度指示stringSarray见优化格式说明array[object见优化格式说明map{由表可见库会按值本身所处的区间而不是“值的 C 类型”去选择标记——例如 0…127 的整数无论来自number_integer还是number_unsigned都编码为iint8128 落在Uuint8超过 int64 上界的无符号数则回退到 high-precision 类型H以字符串形式承载十进制表示。这就是前文示例中1..8都只占 1 个数值字节 1 个i前缀的原因。关于该映射的两点补充说明同样出自格式文档映射完整性serialization任何 JSON 值类型都能被转换为一个 UBJSON 值不存在“编码不了的类型”且to_ubjson产出的任何字节流都能被from_ubjson成功解析保证双向自洽理论尺寸上限超过 9223372036854775807 字节的字符串无法转换该限制在实践上几乎不可达。七、序列化边界行为与注意点针对 UBJSON 编码的“特例”格式文档做了明确交代使用to_ubjson时应心中有数NaN / Infinity 会正常编码如果 JSON 数值中存有 NaN 或 Infinityto_ubjson会如实以 float64D写出。这一点与dump()截然不同——文本形式的dump()会把 NaN/Infinity 序列化为null。做跨格式一致性校验时切勿混淆这两种行为。容器优化与接收端配合开启use_size/use_type后输出的不再是“自闭合容器”解析端必须支持对应的优化格式。好消息是库内的from_ubjson明确支持带优化的数组与对象见 docs/mkdocs/docs/features/binary_formats/ubjson.md 的反向映射表因此“本库编码 本库解码”不受影响。不使用的标记序列化不会产生Zno-op也不会用C单字节字符串标记——单字节字符串仍以S标记写出。二进制值binary valuesUBJSON 这一路不支持二进制值与子类型。若 JSON 数据中含有二进制类型其值会被当作“整数列表”编码遵循 UBJSON 文档的建议写法这会导致“含二进制值的 JSON → UBJSON → JSON”往返后得到与原来不同的对象。对比来看CBOR、MessagePack、BSON 均支持二进制值BJData 与 UBJSON 均不支持见 docs/mkdocs/docs/features/binary_formats/index.md。八、体积表现与格式横向对比在 docs/mkdocs/docs/features/binary_formats/index.md 给出的对比数据中UBJSON 是当前仓库支持的 5 种二进制格式UBJSON、CBOR、MessagePack、BSON、BJData之一其体积以 minified JSON 为 100% 基准在canada.json上约为 53.2%在twitter.json上约为 91.3%在citm_catalog.json上约为 78.2%在jeopardy.json上约为 96.6%而开启 size 优化后分别约为 58.6%/92.3%/86.8%/97.4%sizetype优化约为 55.9%/92.3%/85.0%/95.0%。这一数据恰好印证了前文所述——use_size单独使用可能不降反升而use_type在同构容器上能显著挽回体积。需要注意这些比例针对特定测试数据集实际收益高度依赖数据本身的同构程度与数值分布选择优化参数前应对自己的数据做实测。九、配套 API 与版本历史to_ubjson在库中并非孤例它与同一 API 家族共同构成“二进制序列化”工具集源码集中在 include/nlohmann/json.hpp 的binary serialization/deserialization区块方向格式对应静态函数编码UBJSONto_ubjson解码UBJSONfrom_ubjson编码CBORto_cbor编码MessagePackto_msgpack编码BSONto_bson编码BJDatato_bjdata版本历史to_ubjson自3.1.0版本起加入该库。若你的工程同时涉及 BJData可以留意to_bjdata其实现与 UBJSON 共用write_ubjson编码器仅追加use_bjdata开关与版本参数。十、快速上手建议一个典型的“编码 → 传输 → 解码”闭环形如下面的代码可直接替换示例中的数组部分进行验证#include nlohmann/json.hpp #include vector using json nlohmann::json; // 编码启用 sizetype 优化以压缩同构数组 json payload {{id, 42}, {tags, {c, json, binary}}}; std::vectorstd::uint8_t bytes json::to_ubjson(payload); // 解码对端或本进程任意时刻 json restored json::from_ubjson(bytes);无论选择默认编码还是优化编码只要保持“编码参数与解码能力”一致to_ubjson/from_ubjson就能提供一条完整、线性复杂度且异常安全的二进制数据通道。在需要极致的同构容器压缩率时开启use_size use_type在需要兼容各类 UBJSON 解析器时使用默认参数是两条最稳妥的实践路线。【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考