
nlohmann::basic_json::to_bjdata 指南用 JSON for Modern C 将 JSON 序列化为二进制 BJData 格式【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/jsonto_bjdata是nlohmann::basic_json提供的静态成员函数负责把 JSON 值序列化为BJDataBinary JData二进制字节流目标是在保持比 JSON 更紧凑的同时获得更高的解析与传输效率。本文以 to_bjdata.md 为骨架结合 BJData 格式说明页、单头文件实现与单元测试完整讲解该 API 的函数签名、参数语义、类型映射、优化容器、ND-array 与二进制值处理读完可直接在你的 C 项目中接入 BJData 编解码。BJData 是什么与 UBJSON 的关系在深入to_bjdata之前需要先理解它序列化所依据的格式。据 BJData 说明文档BJData 由 [Universal Binary JSONUBJSONDraft 12] 规范衍生并改进而来核心差异有三点引入优化数组容器用于高效存储 N 维紧凑数组ND-arrays打包数组新增 5 个类型标记[u]uint16、[m]uint32、[M]uint64、[h]float16、[B]byte用于无歧义地映射常见二进制数值类型改用小端字节序little-endianLE存储所有数值而非 UBJSON 的大端序从而在绝大多数现代 x86/ARM 平台上省去字节交换开销。与 MessagePack、CBOR 等二进制格式相比BJData/UBJSON 同时具备“二进制 准人类可读”的罕见特性其语义元素类型标记、名字/字符串类型都是直接可读的 ASCII 字符。这意味着 BJData 数据不仅体积紧凑、读写快速还可以用简单手段直接检索或阅读。to_bjdata的序列化目标就是这一格式。仓库在 src/ 的模糊测试目标、单元测试 unit-bjdata.cpp 中都对该格式做了大量往返校验。函数签名与参数详解to_bjdata在 basic_json 类声明 中提供两组共三个重载静态调用无需实例化对象// (1) 返回字节向量 static std::vectorstd::uint8_t to_bjdata(const basic_json j, const bool use_size false, const bool use_type false, const bjdata_version_t version bjdata_version_t::draft2); // (2) 写入输出适配器 static void to_bjdata(const basic_json j, detail::output_adapterstd::uint8_t o, const bool use_size false, const bool use_type false, const bjdata_version_t version bjdata_version_t::draft2); static void to_bjdata(const basic_json j, detail::output_adapterchar o, const bool use_size false, const bool use_type false, const bjdata_version_t version bjdata_version_t::draft2);参数类型含义默认值jconst basic_json待序列化的 JSON 值—ooutput_adapterstd::uint8_t/output_adapterchar写入序列化结果的输出适配器—use_sizebool是否给容器类型添加尺寸标注优化格式可选falseuse_typebool是否给容器类型添加类型标注必须与use_size true配合可选falseversionbjdata_version_t使用哪个版本的 BJData针对二进制值的 draft3 编码可选bjdata_version_t::draft2从源码实现看第一组重载只是创建内部字节向量并委托给第二组static std::vectorstd::uint8_t to_bjdata(const basic_json j, const bool use_size false, const bool use_type false, const bjdata_version_t version bjdata_version_t::draft2) { std::vectorstd::uint8_t result; to_bjdata(j, result, use_size, use_type, version); return result; }而后两个 void 重载都通过binary_writer完成实际写入见 json.hpp 对应实现static void to_bjdata(const basic_json j, detail::output_adapterstd::uint8_t o, const bool use_size false, const bool use_type false, const bjdata_version_t version bjdata_version_t::draft2) { binary_writerstd::uint8_t(o).write_ubjson(j, use_size, use_type, true, true, version); }注意最后一个布尔实参use_bjdata trueto_bjdata内部复用的是与 UBJSON 共用的binary_writer::write_ubjson()通道见 binary_writer 的 write_ubjson 定义通过该开关与bjdata_version切换 BJData 语义。返回值与参数行为重载 (1)返回包含 BJData 序列化内容的std::vectorstd::uint8_t重载 (2)无返回值结果直接写入给定的输出适配器o可用于流式输出到文件、网络缓冲等场景。关于use_size与use_type的语义BJData 说明文档 补充得十分明确use_size true会在容器开头加入元素数量信息#标记并删除结尾的闭合标记use_type true会进一步检查容器内所有元素是否同类型若是则在容器开头写入$ 类型标记但该参数只允许与use_size true联用需要提醒的是单独使用use_size true时编码结果可能反而更大——它的收益在于接收方能够立刻得知容器元素个数便于预分配或流式解析。返回/异常/复杂度契约原 API 文档明确给出如下契约异常安全强保证strong guarantee——即便抛出异常JSON 值也不会有任何改动。序列化全程只读访问j不修改内部状态。异常当use_type为true而use_size为false时抛出json.exception.other_error.502。该异常消息为use_type requires use_size true见 exceptions.md在实际实现中由binary_writer通过JSON_THROW(other_error::create(502, use_type requires use_size true, j))触发数组分支、对象分支。复杂度与 JSON 值j的大小成线性关系。JSON → BJData 的类型映射序列化方向类型映射表 给出了本库从 JSON 类型到 BJData 类型的完整对应关系JSON value 类型value/范围BJData 类型markernullnullnullZbooleantruetrueTbooleanfalsefalseFnumber_integer-9223372036854775808..-2147483649int64Lnumber_integer-2147483648..-32769int32lnumber_integer-32768..-129int16Inumber_integer-128..127int8inumber_integer128..255uint8Unumber_integer256..32767int16Inumber_integer32768..65535uint16unumber_integer65536..2147483647int32lnumber_integer2147483648..4294967295uint32mnumber_integer4294967296..9223372036854775807int64Lnumber_integer9223372036854775808..18446744073709551615uint64Mnumber_unsigned0..127int8inumber_unsigned128..255uint8Unumber_unsigned256..32767int16Inumber_unsigned32768..65535uint16unumber_unsigned65536..2147483647int32lnumber_unsigned2147483648..4294967295uint32mnumber_unsigned4294967296..9223372036854775807int64Lnumber_unsigned9223372036854775808..18446744073709551615uint64Mnumber_floatany valuefloat64Dstring带最短长度指示stringSarray见优化格式/ND-array 说明array[object见优化格式说明map{binary见二进制值说明array[$B该映射是完备的任何 JSON 值类型都能转换为 BJData 值并且to_bjdata生成的任何 BJData 输出都能被from_bjdata成功解析回 JSON。需要注意的几个边界事实均来自说明文档而非推断未使用的 markerZno-op 值不会产生、C单字节字符串统一用S序列化NaN/Infinity若 JSON number 内存储了 NaN 或 Infinity会被正常序列化这与dump()将 NaN/Infinity 序列化为null的行为不同理论尺寸上限超过 $2^{64}-1$ 字节18446744073709551615 字节的字符串无法转换字节序BJData 中所有数值类型整数UiuImlML与浮点hdD都以小端存储与 UBJSON 的大端形成破坏性差异。完整可运行示例下面程序来自 docs/mkdocs/docs/examples/to_bjdata.cpp覆盖了对象、普通数组、尺寸优化数组、尺寸类型优化数组四种场景#include iostream #include iomanip #include nlohmann/json.hpp using json nlohmann::json; using namespace nlohmann::literals; // 以“诊断格式”打印 BJData可打印 ASCII 直接输出字符其余输出十进制数字 void print_byte(uint8_t byte) { if (32 byte and byte 128) { std::cout (char)byte; } else { std::cout (int)byte; } } int main() { // 创建一个 JSON 对象 json j R({compact: true, schema: false})_json; // 序列化为 BJData std::vectorstd::uint8_t v json::to_bjdata(j); for (auto byte : v) { print_byte(byte); } std::cout std::endl; // 创建数字数组 json array {1, 2, 3, 4, 5, 6, 7, 8}; // 默认表示 std::vectorstd::uint8_t v_array json::to_bjdata(array); // 尺寸优化 std::vectorstd::uint8_t v_array_size json::to_bjdata(array, true); // 尺寸 类型优化 std::vectorstd::uint8_t v_array_size_and_type json::to_bjdata(array, true, true); 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; }对应的标准输出见 to_bjdata.output{i7compactTi6schemaF} [i1i2i3i4i5i6i7i8] [#i8i1i2i3i4i5i6i7i8 [$i#i812345678逐行解读这个输出可以直观看出三种编码策略的差别对象{compact: true, schema: false}{开始 →i7表示下一字符串长度 7int8 编码→compact→Ttrue→i6schema→Ffalse→}结束。全部标记均可读普通数组[i1i2i3i4i5i6i7i8]每个元素自带 int8 类型前缀仅开启use_size[#i8表明容器含 8 个元素随后逐元素编码末尾不再有]同时开启use_size use_type[$i#i8表示“容器内元素类型统一为 int8共 8 个”随后是裸字节1 2 3 4 5 6 7 8因全部小于 33 不可打印而被打印成数字原始含义是每个元素只占一个字节数组由 16 字节压缩为 4 字节前缀 8 字节数据。优化容器的深层实现$与#标记从 write_ubjson 的数组分支 可以看到优化容器在源码层面的运作方式当use_type为真且数组非空时先比较首元素类型前缀first_prefix与其余全部元素std::all_of只有全部一致才进入优化路径写入$ 类型标记并置prefix_required false使后续元素不再重复写前缀当use_count为真时写入# 容器尺寸BJData 对可优化类型有限制源码中显式维护了排除表bjdx {[, {, S, H, T, F, N, Z}见 数组 与 对象 两处。原因是 BJData 中$后允许的类型被限制为非零定长类型即只能是UiuImlMLhdDCB之一[、{容器、S字符串、H高层容器、T/F/N/Z零长类型都因空间节省有限、可读性受损和安全风险而禁止进入优化容器。对象分支的处理逻辑与数组完全对称区别仅在于每个键值对先写键名长度再写键名文本最后递归序列化值见 write_ubjson 对象分支。ND-array 打包数组支持BJData 把 UBJSON 的优化数组尺寸标记扩展为支持同类型 N 维打包数组。以 2 维uint8数组[[1,2],[3,4],[5,6]]为例说明文档给出的对比是UBJSON 中嵌套优化数组的写法约为[ [$U#i2 1 2 [$U#i2 3 4 [$U#i2 5 6 ]BJData 中可进一步压成[$U#[$i#i2 2 3 1 2 3 4 5 6或[$U#[i2 i3] 1 2 3 4 5 6。为同时保留维度与类型信息from_bjdata解析 ND-array 时按 JData 规范中的注解数组格式转成 JSON 对象而to_bjdata的方向正好相反——当 JSON 对象呈现为下述形态时会自动压缩为紧凑的 BJData ND-array{ _ArrayType_: uint8, _ArraySize_: [2,3], _ArrayData_: [1,2,3,4,5,6] }对象触发该转换需满足全部条件见 说明文档_ArrayType_是uint8、int8、uint16、int16、uint32、int32、uint64、int64、single、double、char、byte之一_ArraySize_的每一项都是非负整数且其乘积可表示为一个std::size_t_ArrayData_的元素个数恰好等于上述乘积_ArrayData_的每个元素都是_ArrayType_所声明的数值种类single/double对应浮点其余对应整数。在源码中对应 write_ubjson 的对象分支起始处当use_bjdata且对象恰好含_ArrayType_、_ArraySize_、_ArrayData_三个键时调用私有成员write_bjdata_ndarray()尝试走打包数组编码失败则回退为普通对象序列化。当_ArraySize_中存储的一维向量只含单个整数、或含两个且其中一个为 1 时会退而生成普通 1-D 优化数组。注意当前库版本尚未支持从嵌套 JSON 数组自动检测并转换出 BJData ND-array——ND-array 只能经由上述注解对象格式触发。二进制值与version参数draft2 与 draft3BJData 在优化数组中提供了专用B标记定义于 BJData draft3 规范来指示二进制数据因此与 UBJSON 不同二进制数据在 BJData 中可以完整地往返序列化与反序列化。为保持与BJData Draft 2的兼容draft3 的优化二进制数组必须通过to_bjdata的version参数显式启用。bjdata_version_t枚举定义在 json.hpp/// how to encode BJData enum class bjdata_version_t { draft2, draft3, };并在basic_json中以别名重新暴露json.hpp。写入端通过const bool bjdata_draft3 use_bjdata bjdata_version bjdata_version_t::draft3;判断版本json.hpp。两者对二进制值的编码差异在 write_ubjson 的 binary 分支 中有直接体现Draft2默认若 JSON 数据含二进制类型值会按 BJData 文档建议存为一列整数。具体实现是每个字节前面带上Uuint8前缀逐一写出这同时意味着含二进制值的 JSON 经 BJData 往返后得到的 JSON 对象可能不同会变成整数列表Draft3在use_type启用时写$B前缀随后直接写裸字节对应真正意义上的二进制数组。示例如下// draft2二进制值退化为带 U 前缀的整数列表 json j_bin json::from_msgpack(...); // 示意构造含 binary 的值 std::vectorstd::uint8_t d2 json::to_bjdata(j_bin); // draft2默认 std::vectorstd::uint8_t d3 json::to_bjdata(j_bin, true, true, json::bjdata_version_t::draft3); // draft3 优化二进制数组需要二进制数据无损往返的场景应显式选择draft3并同时开启use_size/use_type。测试验证与往返一致性仓库的 BJData 单元测试 tests/src/unit-bjdata.cpp 对to_bjdata的完整性做了大规模往返校验核心断言模式如下对应文件中大量SECTIONconst auto result json::to_bjdata(j); CHECK(json::from_bjdata(result) j); CHECK(json::from_bjdata(result, true, false) j);即“先to_bjdata再from_bjdata结果必须与原 JSON 相等”覆盖整数各档位范围、布尔、对象、嵌套数组等类型。此外测试还校验了 binary_reader 的 BJData 查找表有序性bjdata_lexer用于解析 BJData marker以及input_format_t::bjdata解析入口。另有专门的模糊测试目标 fuzzer-parse_bjdata.cpp 持续对抗性验证解析健壮性。如果你使用模块化源码而非单头文件相关声明位于 include/nlohmann/json.hpp 对应的basic_json类中实现代码则散落在include/nlohmann/detail/output/binary_writer.hpp等 detail 目录下single_include/nlohmann/json.hpp 是上述源码合并后的单头版本。相关 API 与版本历史to_bjdata属于 basic_json 的二进制序列化 API 家族与下列函数配套使用from_bjdata从 BJData 输入创建 JSON 值to_bjdata的逆操作to_cbor、to_msgpack、to_bson、to_ubjson将 JSON 值序列化为 CBOR / MessagePack / BSON / UBJSON 的兄弟函数。版本历史以当前仓库 ChangeLog.md 与 API 文档为准该 API 在3.11.0版本中首次加入面向 draft3 二进制编码的BJDataversion参数在3.12.0版本中加入。实践中若传输双方都使用支持完整类型体系的系统可优先开启use_size use_type获得最紧凑的流若需保留二进制类型与 ND-array 能力请选择bjdata_version_t::draft3而追求格式可读、便于调试排查时默认的 draft2 普通编码是最直观的选择。【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考