JSON for Modern C++ 中的 BJData 反序列化:nlohmann::json::from_bjdata 全面解析与实战
发布时间:2026/9/8 16:40:22来源:尧图网络
JSON for Modern C 中的 BJData 反序列化nlohmann::json::from_bjdata 全面解析与实战【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/jsonfrom_bjdata是 nlohmann/jsonJSON for Modern C提供的静态反序列化接口负责把遵循BJDataBinary JData规范的二进制字节流还原为json值。本文基于官方 API 文档与仓库源码系统讲解两个重载的签名与参数语义、底层解析实现、异常与错误处理并结合类型映射、ND-array 注释格式、可运行示例与单元测试帮助你在消息传输、科学计算数组打包、跨语言二进制交换等场景中正确使用该接口。读完你将能够用字节容器 /std::istream/FILE*/ 迭代器区间完成 BJData 解码正确处理strict与allow_exceptions两种开关并对齐 BJData 与 UBJSON、MessagePack 等兄弟格式的取舍。BJData 与 from_bjdata 的定位BJData 是 UBJSONUniversal Binary JSONDraft 12ND-array多维打包数组优化容器用于高效存储同构数值的多维数组5 个新类型标记[u](uint16)、[m](uint32)、[M](uint64)、[h](float16)、[B](byte)从而无歧义地表达常见的二进制数值类型统一采用小端little-endian字节序存储数值替代 UBJSON 的大端序避免在主流小端平台上做不必要的字节交换。与 MessagePack、CBOR 等二进制 JSON 变体相比BJData 与 UBJSON 有一个罕见组合特性既是二进制又“准人类可读”。因为数据中的语义元素类型标记、字符串名称都是可直接阅读的 ASCII 字符因此数据不仅紧凑、读写快还可以用简单的文本工具直接检索与阅读。from_bjdata作为该库处理 BJData 的入口与序列化方向的 to_bjdata 相对应是 BJData 完整编解码闭环中的“解码半边”。官方文档明确任意 JSON 值都可转换为 BJData反过来任意由to_bjdata产生的 BJData 流也都能被from_bjdata成功解析映射是完整的。函数签名与模板语义函数定义于 include/nlohmann/json.hpp是basic_json的静态成员共两个重载// (1) 单输入源 templatetypename InputType static basic_json from_bjdata(InputType i, const bool strict true, const bool allow_exceptions true); // (2) 迭代器区间 / C20 ranges异构 sentinel templatetypename IteratorType, typename SentinelType IteratorType static basic_json from_bjdata(IteratorType first, SentinelType last, const bool strict true, const bool allow_exceptions true);重载 (1)从一个“兼容输入”读取数据重载 (2)从迭代器区间读取当IteratorType与SentinelType为不同类型、且可用operator!比较时即成为 C20 ranges 支持例如std::counted_iterator搭配std::default_sentinel_t。模板参数InputType重载 1凡是能转换为输入适配器input adapter的类型皆可官方文档列出的实例包括一个std::istream对象一个FILE*指针一个 C 风格字符数组一个指向单字节字符、以空字符结尾的字符串的指针一个容器obj其begin(obj)/end(obj)能产生一对合法迭代器通过 ADL 或成员函数查找语义与std::begin/std::end兼容。IteratorType兼容的迭代器类型。SentinelType默认为IteratorType也可以是与IteratorType通过operator!可比较的不同类型典型用途有为 C20 ranges 提供的自定义 sentinel当IteratorType是std::counted_iterator时使用std::default_sentinel_t。函数参数参数方向含义iin可转换为输入适配器的 BJData 格式输入firstin指向输入起始位置的迭代器lastin指向输入结束位置的迭代器或与末尾迭代器经operator!判等的 sentinelstrictin是否要求输入被完整消费至 EOF默认trueallow_exceptionsin解析出错时是否抛出异常可选默认true返回值返回反序列化得到的 JSON 值。若发生解析错误且allow_exceptions false返回值为value_t::discarded可用 is_discarded 检测json j json::from_bjdata(bad_input, true, false); if (j.is_discarded()) { // 解析失败按业务需要降级处理 }底层调用链从静态方法到 BJData 解析器from_bjdata的实现非常薄真实工作全部委托给内部解析设施。以重载 (1) 为例include/nlohmann/json.hpptemplatetypename InputType static basic_json from_bjdata(InputType i, const bool strict true, const bool allow_exceptions true) { basic_json result; auto ia detail::input_adapter(std::forwardInputType(i)); detail::json_sax_dom_parserbasic_json, decltype(ia) sdp(result, allow_exceptions); const bool res binary_readerdecltype(ia)(std::move(ia), input_format_t::bjdata) .sax_parse(input_format_t::bjdata, sdp, strict); return res ? result : basic_json(value_t::discarded); }其核心链路可拆成四步detail::input_adapter(...)把任意的InputTypestd::istream、FILE*、字符数组、带begin/end的容器等统一包装为输入适配器。重载 (2) 则调用detail::input_adapter(std::move(first), std::move(last))将迭代器区间同样包装成适配器因此两种重载最终走同一套解析逻辑。json_sax_dom_parserbasic_json, decltype(ia) sdp(result, allow_exceptions)构造 SAX 事件驱动的 DOM 组装器它把解析器吐出的“开始数组 / 键 / 数值 / 字符串”等事件逐步还原为树形json值并接收allow_exceptions控制是否抛异常。binary_readerdecltype(ia)(std::move(ia), input_format_t::bjdata)以格式枚举input_format_t::bjdata构造二进制读取器。该枚举定义于 include/nlohmann/detail/input/input_adapters.hppenum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata };。.sax_parse(input_format_t::bjdata, sdp, strict)真正的字节级解析。值得注意的底层事实是由于BJData 是 UBJSON 的派生格式仓库在 include/nlohmann/detail/input/binary_reader.hpp 中让 BJData 与 UBJSON 共用同一个内部解析函数parse_ubjson_internal()并以if (input_format ! input_format_t::bjdata)之类的分支区分两者的差异点。也就是说源码层面把 BJData 视为“UBJSON 的一个超集变体”来实现这与格式定义文档的描述完全一致。解析器中的 BJData 专属差异在共用的parse_ubjson_internal及其辅助函数中可以观察到 BJData 特有行为的实现证据额外数值标记解析分支允许 BJData 使用u/m/M/h/B等 UBJSON 没有的标记见 binary_reader.hpp 中 2000–2300 行附近的字符串长度取值与数值读取分支小端字节序读取整数与浮点时会根据格式决定是否翻转字节序代码中可见is_little_endian ! (InputIsLittleEndian || format input_format_t::bjdata)的判定即 BJData 固定按小端解释数值ND-array 识别get_ubjson_size_type()在 BJData 下会检查容器 size 标记的最高位1 8以识别打包数组packed array / ND-array一旦识别会进一步读取维度信息并在 binary_reader.hpp 附近把 ND-array 编码为JData 注释数组格式的对象交给 SAX 处理器。这种“薄封装 深度委托”的设计带来的直接好处是所有解析错误过早结束、非法字节、字符串读取失败等都会统一通过 SAX 的parse_error回调上报再由json_sax_dom_parser依据allow_exceptions决定抛出异常还是静默返回discarded错误语义在不同二进制格式CBOR/MessagePack/UBJSON/BJData/BSON间保持一致。异常与异常安全异常安全官方文档给出强保证strong guarantee若抛出异常JSON 值不会发生任何改变。可能抛出的异常依据文档与源码异常经由 binary_reader.hpp 中parse_error::create(...)各处上报from_bjdata可能抛出以下异常完整异常族说明见 docs/mkdocs/docs/home/exceptions.md异常触发场景parse_error.110输入过早结束或当strict true时数据未消费到 EOF即存在多余尾部字节parse_error.112发生解析错误例如遇到非法的类型标记字节parse_error.113字符串无法被成功解析长度读取失败、字符串内容异常等out_of_range.408优化容器或 n 维数组的 size 无法用std::size_t表示尺寸溢出在 tests/src/unit-bjdata.cpp 中可以找到对应的验证用例例如截断的 float16 输入会命中 110CHECK_THROWS_WITH_AS(_ json::from_bjdata(vec0), [json.exception.parse_error.110] parse error at byte 2: ... unexpected end of input, json::parse_error); CHECK(json::from_bjdata(vec0, true, false).is_discarded());同一用例同时展示了在allow_exceptions false时改为返回discarded值而非抛出。strict 参数的作用strict为true默认时解析器要求输入被完整消费如果整个输入被解析完却仍未到达 EOF或读到输入末尾时数据结构尚未闭合都会触发parse_error.110。这在需要确保没有多余尾随字节的协议场景例如“一帧 恰好一条消息”中尤为有用而当你从大缓冲区中解析第一条消息、允许其后还有别的数据时可考虑关闭严格模式。完整示例字节向量解码官方为from_bjdata提供的可运行示例位于 docs/mkdocs/docs/examples/from_bjdata.cpp#include iostream #include iomanip #include nlohmann/json.hpp using json nlohmann::json; int main() { // create byte vector std::vectorstd::uint8_t v {0x7B, 0x69, 0x07, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x54, 0x69, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x69, 0x00, 0x7D }; // deserialize it with BJData json j json::from_bjdata(v); // print the deserialized JSON value std::cout std::setw(2) j std::endl; }该字节序列内部是一个 BJData 对象0x7B与0x7D分别是对象容器{、}的标记0x54(T)、0x69数值等字节则编码了布尔与整数成员。输出为对应 from_bjdata.output{ compact: true, schema: 0 }编译运行方式假定在仓库根目录且工作区已安装 C11 及以上编译器g -stdc11 -I single_include docs/mkdocs/docs/examples/from_bjdata.cpp -o from_bjdata_demo ./from_bjdata_demo这里-I single_include指向聚合头文件 single_include/nlohmann/json.hpp即头文件库的标准引入方式。迭代器区间与 istream 变体from_bjdata的第一个重载接受任何“兼容输入”因此在上述示例之外同样的数据还可以用这些等价的写法读取// 用 std::istream 读取例如 std::ifstream / std::stringstream json j1 json::from_bjdata(iss); // 用迭代器区间重载 2 json j2 json::from_bjdata(v.begin(), v.end()); // 用 C 风格字符数组 std::uint8_t raw[] {0x7B, 0x69, 0x07, /* ... */ 0x7D}; json j3 json::from_bjdata(raw);注意重载 (2) 的两个可选参数与重载 (1) 完全一致因此json::from_bjdata(v.begin(), v.end(), false)之类“非严格 不抛异常”的组合也始终可用。测试代码 tests/src/unit-bjdata.cpp 中大量使用了json::from_bjdata(result, true, false)这种形态来做 round-trip 断言。BJData → JSON 类型映射反序列化方向的映射由 docs/mkdocs/docs/features/binary_formats/bjdata.md 归纳为下表from_bjdata依据它把每个 BJData 标记还原为对应 JSON 值BJData 类型JSON 值类型标记no-op不产生值继续读下一个值NnullnullZfalsefalseFtruetrueTfloat16number_floathfloat32number_floatdfloat64number_floatDuint8number_unsignedUint8number_integeriuint16number_unsigneduint16number_integerIuint32number_unsignedmint32number_integerluint64number_unsignedMint64number_integerLbytenumber_unsignedBstringstringScharstringCarrayarray支持优化格式[ND-arrayobjectJData 注释数组格式[$.#[.objectobject支持优化格式{binarybinary强类型字节数组[$B几个值得强调的细节no-opN解码时不产生任何 JSON 值仅表示“跳过读取下一个值”这是 UBJSON/BJData 家族为流式场景保留的填充语义char(C) 还原为 string、byte(B) 还原为 number_unsigned保证类型无损映射是完整的任何 BJData 值都能转换为一个 JSON 值这正是官方“from_bjdata能解析一切to_bjdata输出”保证的另一半。ND-array 反序列化与 JData 注释数组格式BJData 最有特色的能力是ND-array 打包数组。以二维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。为了在 JSON 侧保留其类型与维度信息from_bjdata在解析这类数据时会把 ND-array 转换成JData 注释数组格式annotated array format的 JSON 对象。上述二维数组解码后形如{ _ArrayType_: uint8, _ArraySize_: [2,3], _ArrayData_: [1,2,3,4,5,6] }反之亦然当 to_bjdata 遇到这种形态的对象时会自动把它压缩回紧凑的 BJData ND-array。其中当_ArraySize_只含一个整数、或含两个整数但其中一个为1时生成的是一维优化数组而非 ND-array。只有当注释确实描述了一个可打包的数组时才会被转换这要求同时满足_ArrayType_必须是uint8、int8、uint16、int16、uint32、int32、uint64、int64、single、double、char、byte之一_ArraySize_的每一项都是非负整数且它们的乘积可以表示为std::size_t_ArrayData_恰好包含指定数量的元素_ArrayData_的每个元素都属于_ArrayType_所声明的数值种类single/double为浮点数其余为整数。另外注意一个明确的版本能力边界来自官方文档当前版本尚不支持自动识别并把“嵌套 JSON 数组”直接转换为 BJData ND-array——识别只发生在反向_ArrayType_注释对象 → ND-array这一方向。优化容器与优化类型的限制容器数组 / 对象的优化格式由序列化端两个参数控制详见 to_bjdatause_size在容器开头写入元素个数并省略结束标记use_type进一步检查容器内元素是否同型是则在开头写入类型标记必须与use_size true搭配使用。需要提醒仅开启use_size反而可能让表示变大它的价值在于接收端能立刻得知元素个数便于预分配与流式处理。在 BJData 中紧随$标记优化容器类型指示之后的合法类型被严格限制为非零定长类型即只能是UiuImlMLhdDCB。可变长类型[{SH和零长类型TFN不允许出现在优化容器中——这一限制的动机是节省空间、保持可读性并降低安全风险。二进制值binary与版本兼容BJData 为二进制数据定义了专用标记B优化数组内使用因此与 UBJSON 不同二进制数据在 BJData 中既可以序列化也可以反序列化。需要注意版本差异相关映射见“BJData → JSON 类型映射”表中[$B → binaryDraft 3 模式需通过to_bjdata的version参数显式开启下二进制值以强类型字节数组形式编解码**Draft 2 模式默认**下JSON 中的二进制值会被当作整数列表存储这意味着含二进制值的 JSON 往返一次后对象形态会发生变化。同时编码数字浮点方面还有一条与文本dump()的差异若 JSON 数字中存储了 NaN 或 Infinityto_bjdata/from_bjdata能按 IEEE 浮点正常往返而文本dump()会将它们序列化为null。复杂度与适用前提时间复杂度线性于输入字节数Linear in the size of the input符合二进制格式一次扫描即可还原为 DOM 的特性。版本前提from_bjdata自3.11.0加入3.13.0 起重载 (1) 扩展了容器输入支持可接受仅具备 lvalue-only ADLbegin/end、语义与std::begin/std::end一致的类型3.13.0 起重载 (2) 扩展为接受异构迭代器 sentinel 组合C20 ranges 支持。请据此选择匹配的库版本。环境前提json.hpp是仅头文件的 C11 库聚合版位于 single_include/nlohmann/json.hpp开箱即用。测试与质量保障仓库针对 BJData 的测试主要位于 tests/src/unit-bjdata.cpp它会针对各种 JSON 值调用to_bjdata生成字节流再回读断言等价形如CHECK(json::from_bjdata(result) j)并同时覆盖strict false与allow_exceptions false的组合、float16 特殊值-0.0、65504.0等、非法字节与截断输入、超大数字溢出等负例。此外 tests/src/unit-32bit.cpp 验证 32 位平台上的尺寸语义tests/src/fuzzer-parse_bjdata.cpp 提供针对 BJData 解析器的模糊测试入口配合 tests/Makefile 的 fuzz 目标使用共同保障了解析器的健壮性。相关 API 一览BJData 并非孤岛——from_bjdata与下面这些接口构成完整的二进制格式矩阵可按需组合to_bjdata把 JSON 值序列化为 BJDatafrom_cbor从 CBOR 输入创建 JSON 值from_msgpack从 MessagePack 输入创建 JSON 值from_bson从 BSON 输入创建 JSON 值from_ubjson从 UBJSON 输入创建 JSON 值。选择建议在需要多维数组紧凑打包或二进制仍可人工阅读的场合如科学计算、仪器数据、配置快照交换BJData 是这些格式中特性最贴近需求的选项实际落地时请结合to_bjdata的use_size/use_type/version参数与本文的strict/allow_exceptions开关设计出能“一次编解码无损往返”且错误处理可控的数据通路。【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网