JSON for Modern C++(nlohmann/json)SAX 接口详解:json_sax::end_object 对象结束事件与解析中止机制

JSON for Modern C++(nlohmann/json)SAX 接口详解:json_sax::end_object 对象结束事件与解析中止机制 JSON for Modern Cnlohmann/jsonSAX 接口详解json_sax::end_object 对象结束事件与解析中止机制【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json本篇基于 JSON for Modern Cnlohmann/json 官方 API 文档中的 json_sax::end_object 条目展开讲解json_sax纯虚回调家族中负责对象结束的事件如何被文本解析器与二进制格式解析器触发、其返回值如何控制整个sax_parse流程是否继续并借助仓库头文件、单元测试与官方示例让你能够独立实现一个正确实现end_object()的自定义 SAX 消费者并用它完成流式解析、提前中止与事件计数等任务。一、end_object在 SAX 接口家族中的定位在 nlohmann/json 中SAXSimple API for XML 思想在 JSON 上的移植是一组面向解析过程事件的纯虚回调。接口模板定义在头文件 include/nlohmann/detail/input/json_sax.hpp 中templatetypename BasicJsonType struct json_sax { // ... virtual bool end_object() 0; // ... };end_object的函数原型见 json_sax.hpp在文档中描述为virtual bool end_object() 0;它的语义一句话即可概括一个 JSON object花括号包裹的键值对容器已经完整读完。与之成对出现的是start_object读到{、以及逐成员进入的key/value回调而数组的对应事件是 end_array。完整接口清单可参见 json_sax 索引文档。json_saxBasicJsonType中的所有回调null、boolean、number_*、string、start_object、key、end_object、start_array、end_array、parse_error等都是纯虚函数因此任何自定义消费者都必须逐一实现。为此basic_json内置了便捷别名include/nlohmann/json.hppusing json_sax_t json_saxbasic_json;官方示例docs/mkdocs/docs/examples/sax_parse.cpp中正是让自定义类继承json::json_sax_t其注释也指出继承并非强制但可以避免遗漏某个必须实现的函数。版本信息end_object与整套json_sax接口均从3.2.0版本开始提供接口对二进制值类型的支持则是在 3.8.0 加入。二、何时触发JSON 解析器中的调用点2.1 文本 JSON 的调用链当以默认input_format_t::json文本格式调用 sax_parse 时实际驱动 SAX 事件的是预测性 LL(1) 文本解析器 include/nlohmann/detail/input/parser.hpp。在对象解析逻辑中一旦词法分析返回token_type::end_object即读到}解析器便回调消费端// 对象内部成员序列解析完毕读到 } 时 if (get_token() token_type::end_object) { if (JSON_HEDLEY_UNLIKELY(!sax-end_object())) { return false; // 消费者要求停止 } // ... 否则继续 }在 parser.hpp 及该文件后面对象收尾的另一处parser.hpp可以看到同样的模式sax-end_object()返回false时解析器立刻return false不再消费剩余输入。对嵌套对象而言最内层对象先结束因此end_object()会从内向外按深度优先顺序被多次回调——输入里有多少层对象闭合就有多少次end_object()。2.2 二进制格式同样会产生该事件nlohmann/json 的 SAX 解析不仅支持 JSON 文本也支持 CBOR、MessagePack、UBJSON、BJData、BSON 等二进制格式。这些格式的读取器统一在 include/nlohmann/detail/input/binary_reader.hpp 中实现各格式在解析完一个 object映射/map 结构后同样会调用sax-end_object()例如 binary_reader.hppBSON 文档、以及同文件中 L1309、L1939、L2701、L2842 等多个代表调用点对应各格式对象收尾逻辑。也就是说无论输入是文本 JSON 还是二进制格式只要 SAX 消费端实现了end_object()它就能在每次对象结束时收到通知——这正是跨格式统一事件处理的基础。2.3 静态约束接口不实现就无法编译如果自定义 SAX 类型并未继承json_sax而是仅实现了其中部分函数编译期检测工具会通过 SFINAE 检查该类型是否具备全部必需签名。include/nlohmann/detail/meta/is_sax.hpp 中定义了templatetypename T using end_object_function_t decltype(std::declvalT().end_object()); // ... static_assert(is_detected_exactbool, end_object_function_t, SAX::value, Missing/invalid function: bool end_object());即要求SAX::end_object()必须存在且精确返回bool见 is_sax.hpp。这是鸭子类型式接口编译期契约的体现继承json::json_sax_t可以避免踩坑。三、返回值语义sax_parse的提前中止开关文档对end_object的返回值只有一句话Whether parsing should proceed是否继续解析。这句话的内涵需要结合sax_parse的整条数据流理解所有 SAX 回调包括end_object返回true表示事件已处理继续解析下一个 token返回false表示消费者主动要求终止解析器立刻停止并逐层退出sax_parse的最终返回值等于最后一个被处理的 SAX 事件的返回值见 sax_parse 文档 的 Return value 一节。因此end_object()返回false是在对象闭合处截断解析的惯用手段。仓库单元测试 tests/src/unit-class_parser.cpp 中的SaxCountdown类就专门验证这一点——它让每个事件把计数器减一class SaxCountdown : public nlohmann::json::json_sax_t { public: explicit SaxCountdown(const int count) : events_left(count) {} bool null() override { return events_left-- 0; } // ... 其他事件类似均返回 events_left-- 0 bool end_object() override { return events_left-- 0; // 计数归零时返回 false中止解析 } // ... };对应断言unit-class_parser.cppSECTION(SAX parser) { SECTION(} without value) { SaxCountdown s(1); CHECK(json::sax_parse({}, s) false); // start_object 消耗计数后end_object 返回 false } SECTION(} with value) { SaxCountdown s(3); CHECK(json::sax_parse({\k1\: true}, s) false); } // ... }当计数归零、end_object()或其他事件返回false时sax_parse整体返回false。该特性可用于实现只解析到某个对象边界就停止的场景例如从大文件中读取第一个完整配置对象后立即结束避免继续构建整个 DOM。四、库内部的三套内置实现对照理解内置实现有助于设计自定义消费者。同一头文件 json_sax.hpp 中定义了三种标准 SAX 实现它们的end_object各有分工json_sax_dom_parserDOM 构建器——json_sax.hpp。普通json::parse()走的就是它解析过程中用ref_stack维护嵌套层级end_object()到来时断言栈顶确实是一个 object可选地记录诊断位置JSON_DIAGNOSTIC_POSITIONS调用set_parents()建立父子关系然后pop_back()弹栈。由此{}结束即代表该层对象已构造完毕。json_sax_dom_callback_parser带回调的 DOM 构建器——json_sax.hpp。它在end_object()时把已构造好的整个对象交给用户回调事件类型parse_event_t::object_end若回调返回false该对象会被置为discarded并从父容器中移除。这正是json::parse(json, filter_callback)过滤接口的底层机制。json_sax_acceptor只验证合法性——json_sax.hpp。json::accept()使用它所有事件一律返回trueend_object()也不例外目的是只检查语法、不建树。对比三者可以发现一个通用设计准则end_object是对象作用域结束的边界信号——DOM 构建器在此收束该层节点回调构建器在此询问用户要不要保留校验器则在此确认结构闭合即可。五、官方示例事件消费者中实现并验证end_object文档页以 sax_parse.cpp 作为完整示例展示如何实现一个记录所有 SAX 事件的自定义消费者并驱动sax_parse。完整源码如下#include iostream #include iomanip #include sstream #include nlohmann/json.hpp using json nlohmann::json; // a simple event consumer that collects string representations of the passed // values; note inheriting from json::json_sax_t is not required, but can // help not to forget a required function class sax_event_consumer : public json::json_sax_t { public: std::vectorstd::string events; bool null() override { events.push_back(null()); return true; } bool boolean(bool val) override { events.push_back(boolean(val std::string(val ? true : false) )); return true; } bool number_integer(number_integer_t val) override { events.push_back(number_integer(val std::to_string(val) )); return true; } bool number_unsigned(number_unsigned_t val) override { events.push_back(number_unsigned(val std::to_string(val) )); return true; } bool number_float(number_float_t val, const string_t s) override { events.push_back(number_float(val std::to_string(val) , s s )); return true; } bool string(string_t val) override { events.push_back(string(val val )); return true; } bool start_object(std::size_t elements) override { events.push_back(start_object(elements std::to_string(elements) )); return true; } bool end_object() override { events.push_back(end_object()); return true; } bool start_array(std::size_t elements) override { events.push_back(start_array(elements std::to_string(elements) )); return true; } bool end_array() override { events.push_back(end_array()); return true; } bool key(string_t val) override { events.push_back(key(val val )); return true; } bool binary(json::binary_t val) override { events.push_back(binary(val[...])); return true; } bool parse_error(std::size_t position, const std::string last_token, const json::exception ex) override { events.push_back(parse_error(position std::to_string(position) , last_token last_token ,\n ex std::string(ex.what()) )); return false; } }; int main() { // a JSON text auto text R( { Image: { Width: 800, Height: 600, Title: View from 15th Floor, Thumbnail: { Url: http://www.example.com/image/481989943, Height: 125, Width: 100 }, Animated : false, IDs: [116, 943, 234, -38793], DeletionDate: null, Distance: 12.723374634 } }] ); // create a SAX event consumer object sax_event_consumer sec; // parse JSON bool result json::sax_parse(text, sec); // output the recorded events for (auto event : sec.events) { std::cout event \n; } // output the result of sax_parse std::cout \nresult: std::boolalpha result std::endl; }编译运行方式与仓库单头文件目录配合g -stdc11 -I single_include sax_parse.cpp -o sax_parse ./sax_parse官方文档记录的程序输出为sax_parse.outputstart_object(elements18446744073709551615) key(valImage) start_object(elements18446744073709551615) key(valWidth) number_unsigned(val800) key(valHeight) number_unsigned(val600) key(valTitle) string(valView from 15th Floor) key(valThumbnail) start_object(elements18446744073709551615) key(valUrl) string(valhttp://www.example.com/image/481989943) key(valHeight) number_unsigned(val125) key(valWidth) number_unsigned(val100) end_object() key(valAnimated) boolean(valfalse) key(valIDs) start_array(elements18446744073709551615) number_unsigned(val116) number_unsigned(val943) number_unsigned(val234) number_integer(val-38793) end_array() key(valDeletionDate) null() key(valDistance) number_float(val12.723375, s12.723374634) end_object() end_object() parse_error(position460, last_token12.723374634U000A }U000A }], ex[json.exception.parse_error.101] parse error at line 17, column 6: syntax error while parsing value - unexpected ]; expected end of input) result: false对照输出可以提炼出end_object()相关的三个关键观察点事件严格嵌套对称Thumbnail对象闭合触发一次end_object()第 18 行输出Image对象闭合再触发一次最外层对象闭合触发第三次。三次end_object()与三处start_object()严格配对体现了 SAX 事件的括号式流结构——如果每次start_object都要压栈那么每次end_object就应弹栈。JSON 文本不携带元素计数start_object(elements18446744073709551615)中的18446744073709551615正是std::size_t的最大值即元素个数未知的哨兵值源码中为detail::unknown_size()见 json_sax.hpp。如 start_object 文档 所述只有二进制格式才可能在上报元素数量JSON 文本解析一律传未知哨兵所以消费者不能依赖该数值。end_object()之后才轮到根级收尾与错误处理示例输入在完整对象闭合后还残留一个]sax_parse默认strict true要求输入被完整消费于是在三次end_object()之后触发parse_error回调异常信息为[json.exception.parse_error.101] ... unexpected ]; expected end of input。parse_error回调返回falsesax_parse随之返回false——这与返回值为最后一个事件的返回值完全一致。若该输入末尾没有多余的]sax_parse会以true正常结束。六、实践要点速览必须返回boolend_object()的签名必须精确匹配bool end_object()编译器通过 is_sax.hpp 中的static_assert强制校验继承json::json_sax_tjson.hpp可省去逐个核对。用返回值实现读到对象边界即停在end_object()中根据业务条件返回falsesax_parse会立刻停止并返回false返回true则继续。参考 unit-class_parser.cpp 的SaxCountdown测试类。嵌套对象按闭合顺序触发对象结束事件从内到外触发用于计数时需把end_object与start_object配对理解。适用于全部输入格式文本 JSON 走 parser.hppCBOR/MessagePack/UBJSON/BJData/BSON 走 binary_reader.hpp两者都会在对象结束时回调end_object。区分三个内置实现DOM 构建parse、带过滤回调的 DOM 构建、纯语法校验accept对end_object的处理不同自定义消费者可参考它们各自的实现方式json_sax.hpp。版本前提end_object自 3.2.0 起可用与整套json_sax接口同步引入。如需继续深入 SAX 机制可依次阅读 json_sax 接口索引、sax_parse 入口文档 以及配套的 start_object 与 end_array 页面从而完整掌握 SAX 事件流的每个环节。【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考