ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JSON for Modern C++ 解析与异常处理:parse_error 从抛出到兜底的完整指南

JSON for Modern C++ 解析与异常处理:parse_error 从抛出到兜底的完整指南 JSON for Modern C 解析与异常处理parse_error 从抛出到兜底的完整指南【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json本指南基于 nlohmann/jsonJSON for Modern C官方文档 parse_exceptions.md并结合 exceptions.md、sax_interface.md 以及 exceptions.hpp、json.hpp、json_sax.hpp 等源码级实现展开。读者将掌握何时抛出json::parse_error、如何通过try/catch捕获并读取诊断信息、如何用allow_exceptions参数与accept()关闭/规避异常以及如何借助自定义 SAX 接口在无异常环境下完整接管解析错误。文章中的示例均可在仓库 docs/mkdocs/docs/examples 下找到对应.cpp与.output源文件直接编译验证。当输入不是合法 JSON 时nlohmann/json 会抛出类型为json::parse_error的异常。该异常是排查“为什么这段文本解析不过”的最关键入口它同时携带错误在输入中的位置、一段诊断消息以及最后一次读取到的输入片段token。parse()、accept()、sax_parse()三套入口对错误暴露方式不同下面逐一展开。一、parse_error非法输入的统一异常类型1.1 异常类层级与关键成员json::parse_error继承自基类json::exception而json::exception继承自标准库std::exception因此可以用catch (const json::exception)作为“通配符”捕获库抛出的任意异常parse/iterator/type/out_of_range/other_error 五类。其定义位于 include/nlohmann/detail/exceptions.hpp除基类成员外parse_error 提供成员类型含义idconst int异常编号parse 类异常编号为1xx如 101byteconst std::size_t出错位置在输入中的字节下标详见 1.2what()const char*完整诊断消息包含编号、行列、位置、原因与最后读到的 token异常消息由 exceptions.hpp 中parse_error::create()拼接而成典型格式如[json.exception.parse_error.101] parse error at line 1, column 8: syntax error while parsing value - unexpected ]; expected [, {, or a literal1.2 byte 成员出错字节下标语义exceptions.hpp 源码注释 与 exceptions.md 的 Byte index 说明 明确给出了byte的约定对于长度为 n 字节的输入1是第一个字符的下标n1是终止空字符或文件结尾的下标该约定同样适用于按字节向量读取的 CBOR、MessagePack 等二进制格式。换句话说byte记录的是“最后读到的那个字符”的位置而非理想语法中的出错点调试时可结合“行、列”消息一起判断。1.3 parse_error 的编号体系1xxexceptions.md 的 Parse errors 一节 按编号列举了全部 parse_error 场景其触发面不止 JSON 文本101JSON 文本语法错误覆盖输入提前结束、空输入、未转义控制字符、字符串未闭合、非法数字格式、\u后非四位十六进制、非法代理对、非法 UTF-8 字节等——这是最常见的一个104–109JSON PatchRFC 6902与 JSON PointerRFC 6901解析错误如“JSON patch 必须是对象数组”“operation 缺少 value/from 成员”“数组下标01不能以 0 开头”“指针必须以/开头”“~后只能跟0或1”“数组下标必须是数字”等110 / 112–115二进制格式CBOR、MessagePack、UBJSON、BSON、BJData读取阶段错误如字节向量提前结束、遇到非法字节、字符串长度非法、不支持的 BSON 记录类型、UBJSON 高精度数字非法等102 / 103历史上用于代理对/超出 0x10FFFF 码点错误目前已被更详细的 101 取代不再单独触发。需要强调的是parse_error并不只由json::parse()抛出——二进制反序列化入口如from_cbor、from_msgpack同样走1xx错误。官方异常消息的完整示例集见 exceptions.md。1.4 获取完整异常信息的最小示例仓库示例 parse_error.cpp 演示了解析失败时如何取回what()、id、byte三份信息其编译输出见 parse_error.output#include iostream #include nlohmann/json.hpp using json nlohmann::json; int main() { try { // parsing input with a syntax error json::parse([1,2,3,]); } catch (const json::parse_error e) { // output exception information std::cout message: e.what() \n exception id: e.id \n byte position of error: e.byte std::endl; } }输出message: [json.exception.parse_error.101] parse error at line 1, column 8: syntax error while parsing value - unexpected ]; expected [, {, or a literal exception id: 101 byte position of error: 8可以清晰看到三份信息的分工what()给出人可读的完整描述id便于程序按编号分类处理byte给出机器可定位的字节下标。二、处理方式一try/catch 捕获 parse_error推荐用于不可信输入处理“不可信输入”untrusted input例如来自网络的 HTTP body、用户上传的配置文件等时官方文档明确建议始终将解析代码包裹在try/catch块中。json j; try { j json::parse(my_input); } catch (json::parse_error ex) { std::cerr parse error at byte ex.byte std::endl; }注意两点解析成功则j被完整赋值解析失败则进入catch分支j保持原值不被部分写入catch (json::parse_error)只捕获解析错误若把输入先交给别处处理可能还会遇到json::type_error、json::out_of_range等其他编号段的异常可视需要改为捕获更宽泛的json::exception基类。这种模式适合“解析失败不算致命错误、程序还要继续运行”的场景如配置项缺省回退、日志告警也适合将异常向上传播由更外层统一兜底的架构。三、处理方式二allow_exceptions false——让解析失败静默返回某些环境不允许使用异常如部分嵌入式工具链、实时系统或开发者希望避免 try/catch 的流程噪音。此时可以给parse()传入第三个布尔参数allow_exceptions将其关闭。3.1 完整函数签名与参数含义parse() 的源码定义 给出的完整签名是static basic_json parse(InputType i, parser_callback_t cb nullptr, const bool allow_exceptions true, const bool ignore_comments false, const bool ignore_trailing_commas false)各参数作用如下参数默认值说明i—输入可为std::string、std::istream、字符迭代器区间等任意可适配输入cbnullptr可选解析回调parser_callback_t在解析过程中被调用不需要时传nullptrallow_exceptionstrue为false时解析出错不再抛出异常而是返回一个“已丢弃”discarded的值ignore_commentsfalse是否忽略 JSON 文本中的注释非标准扩展ignore_trailing_commasfalse是否容忍数组/对象尾部的逗号非标准扩展在文档给出的用法中第二个参数nullptr表示“不使用回调”第三个参数false关闭异常json j json::parse(my_input, nullptr, false); if (j.is_discarded()) { std::cerr parse error std::endl; }3.2 is_discarded()判断解析是否失败当allow_exceptions false且解析失败时parse()返回一个状态为discarded已丢弃的 JSON 值需要调用is_discarded()判定失败返回true输入非法解析失败返回false解析成功j中即合法的 JSON 值。仓库示例 parse__allow_exceptions.cpp 用同一段含“字符串未闭合”错误的文本分别演示了两种模式的差异#include iostream #include nlohmann/json.hpp using json nlohmann::json; int main() { // an invalid JSON text std::string text R( { key: value without closing quotes } ); // parse with exceptions try { json j json::parse(text); } catch (const json::parse_error e) { std::cout e.what() std::endl; } // parse without exceptions json j json::parse(text, nullptr, false); if (j.is_discarded()) { std::cout the input is invalid JSON std::endl; } else { std::cout the input is valid JSON: j std::endl; } }3.3 底层原理解析器内部如何“消化”错误从源码看parse()最终构造parser并驱动 SAX 风格的 DOM 解析器json.hpp 第 4095 行。是否抛异常的决定点在 json_sax_dom_parser::parse_errortemplateclass Exception bool parse_error(std::size_t /*unused*/, const std::string /*unused*/, const Exception ex) { errored true; static_castvoid(ex); if (allow_exceptions) { JSON_THROW(ex); } return false; }也就是说allow_exceptions true时内部直接JSON_THROW(ex)上抛为false时仅把内部errored置位并返回falseDOM 构建终止最终解析结果保持为 discarded 状态。3.4 代价该模式没有任何诊断信息这是官方明确指出的限制关闭异常后你只能知道“解析失败”拿不到出错字节位置、行号、原因和最后 token——parse_error的所有诊断字段都随异常一起消失了。因此它适合“只要一个布尔结论”的场合如日志审计、批量跳过坏文件排查具体语法问题时还是要回到 try/catch 模式。四、处理方式三accept()——只问合法性、不建 DOM第三种思路是干脆不做 DOM 解析json::accept()只返回一个bool表示输入是否为合法 JSON全程不构造json值因此更轻量。if (!json::accept(my_input)) { std::cerr parse error std::endl; }accept() 的源码定义 显示它内部构造的 parser 传入的就是allow_exceptions false然后调用accept(true)真正执行判断的是 SAX 层的json_sax_acceptor类定义见 json_sax.hpp 第 943 行。它的parse_error实现直接返回false表示“输入非法”不会抛出任何异常。仓库示例 accept__string.cpp 对比了合法与非法输入#include iostream #include iomanip #include nlohmann/json.hpp using json nlohmann::json; int main() { // a valid JSON text auto valid_text R( { numbers: [1, 2, 3] } ); // an invalid JSON text auto invalid_text R( { strings: [extra, comma, ] } ); std::cout std::boolalpha json::accept(valid_text) json::accept(invalid_text) \n; }输出为true false见 accept__string.output。同样地官方提醒accept() 也不提供任何诊断信息。另外注意 accept() 默认走严格模式若需容忍注释或尾逗号可传参json::accept(input, /*ignore_comments*/true, /*ignore_trailing_commas*/true)。从语义上accept()等价于allow_exceptionsfalse之后is_discarded()取反但前者不产生任何中间 JSON 值适合只做“预检”的大输入。五、处理方式四自定义 SAX 接口——既无异常又不丢诊断如果你同时想要“不抛异常”和“完整诊断”前面两种方案都无法两全。终极方案是实现自己的SAX 接口让解析过程以事件回调的方式驱动出错时库调用你实现的parse_error(position, last_token, exception)由你决定如何处理——不需要任何 try/catch。5.1 parse_error 回调接口SAX 接口中出错处理函数签名如下完整接口清单见 sax_interface.mdbool parse_error(std::size_t position, const std::string last_token, const json::exception ex);参数含义参数含义position出错的字节位置std::size_tlast_token出错前最后一次读取到的输入片段ex携带完整诊断消息的json::exception实际为parse_error对象返回值决定解析是否继续出错后通常没有继续解析的意义所以该函数一般返回false终止解析。5.2 完整可运行示例sax_no_exception官方文档parse_exceptions.md给出了一个可直接编译运行的完整示例它在不启用异常的情况下打印位置、诊断消息与最后读到的 token#include iostream #include nlohmann/json.hpp using json nlohmann::json; class sax_no_exception : public nlohmann::detail::json_sax_dom_parserjson { public: sax_no_exception(json j) : nlohmann::detail::json_sax_dom_parserjson(j, false) {} bool parse_error(std::size_t position, const std::string last_token, const json::exception ex) { std::cerr parse error at input byte position \n ex.what() \n last read: \ last_token \ std::endl; return false; } }; int main() { std::string myinput [1,2,3,]; json result; sax_no_exception sax(result); bool parse_result json::sax_parse(myinput, sax); if (!parse_result) { std::cerr parsing unsuccessful! std::endl; } std::cout parsed value: result std::endl; }输出parse error at input byte 8 [json.exception.parse_error.101] parse error at line 1, column 8: syntax error while parsing value - unexpected ]; expected [, {, or a literal last read: 3,] parsing unsuccessful! parsed value: [1,2,3]关键点解读以json_sax_dom_parserjson为基类这样既能复用“把 SAX 事件组装回 DOM 值”的现成逻辑result中已解析出的前缀[1,2,3]依旧可用又能通过其构造函数第二个参数false关闭内部抛异常行为对照 json_sax.hpp 的构造函数重写parse_error把原本“被吞掉”的诊断信息接管过来自己打印——既保留了position、last_token、ex.what()三份诊断又全程不需要 try/catch返回false使sax_parse返回false调用方据此得知“解析不成功”。5.3 sax_parse() 的语义与局限json::sax_parse()的签名json.hpp 第 4163-4173 行为static bool sax_parse(InputType i, SAX* sax, input_format_t format input_format_t::json, const bool strict true, const bool ignore_comments false, const bool ignore_trailing_commas false)需要明确的几点sax_interface.md 有正式说明它只返回一个bool表示最后一次 SAX 事件的返回结果不返回json值解析出错时不会抛出异常——如何处理传入parse_error的异常对象完全由你决定format参数支持input_format_t::json、cbor、msgpack、ubjson、bson、bjdata即同一套 SAX 事件模型也可用于二进制格式从源码看SAX 接口是库的内部统一抽象DOM 解析器json_sax_dom_parser与合法性检查器json_sax_acceptor都基于它实现accept() 与 parse() 不过是它的两种现成实例——这正是你自己实现parse_error处理器的底气所在。六、补充从全局开关到扩展诊断除了解析入口层面的四种处理方式异常体系还提供两组与“诊断能力”相关的全局机制处理解析问题时值得了解详见 exceptions.md6.1 全局关闭异常JSON_NOEXCEPTION如果目标是整个程序完全不使用异常可通过编译器选项-fno-exceptions或定义宏JSON_NOEXCEPTION关闭。此时库内部所有throw退化为abort()调用。若要进一步自定义行为可分别覆盖宏JSON_THROW_USER替代throw、JSON_TRY_USER替代try、JSON_CATCH_USER替代catch。需要注意JSON_THROW_USER的实现必须离开当前作用域例如真正抛出异常或调用abort()因为宏展开后继续执行后续代码可能产生未定义行为。一个常见的“记日志后终止”的替换写法见 exceptions.md 示例。6.2 扩展诊断消息JSON_DIAGNOSTICS默认情况下异常只报告“局部上下文”比如对深层嵌套值做类型访问失败时消息只说“type must be object, but is number”无法直接看出是哪个字段出错。为让消息包含从根到出错值的完整 JSON Pointer 路径如/address/housenumber可以定义宏JSON_DIAGNOSTICS为1再包含头文件#define JSON_DIAGNOSTICS 1 #include nlohmann/json.hpp其代价是为每个 JSON 值额外存储一个指向父节点的指针并维护父链源码逻辑见 exceptions.hpp 第 76-139 行因此默认关闭。启用后不仅 parse_error 中的定位信息更直观type_error、out_of_range等其它异常也会附带精确的 JSON Pointer 上下文显著降低排错成本。6.3 排错小技巧结合 exceptions.md 的建议当拿到parse_error消息却仍困惑时先把输入原样输出到终端确认文件被正确打开、内容确实被完整读入中文、BOM、编码问题常在这里暴露将输入片段粘贴到在线 JSON 校验器或用jq这类命令行 JSON 工具做二次校验快速定位第几层语法不合法若排查深层的类型不匹配问题配合JSON_DIAGNOSTICS开启扩展诊断让异常消息带出完整 JSON Pointer 路径。七、四种方案取舍速查方案是否需要 try/catch是否返回/可取得 JSON 值是否有诊断信息适用场景parse() try/catch需要是成功后j即结果完整what()/id/byte/行号列号处理不可信输入、需要定位语法错误的默认首选parse(..., nullptr, false)is_discarded()不需要是失败为 discarded无无异常环境、只关心成败的批量处理accept()不需要否仅bool无只做“输入是否合法”的轻量预检不建 DOMsax_parse() 自定义 SAX 的parse_error不需要取决于你的 SAX 实现完整且由你全权接管既要无异常又要完整诊断或需要自定义事件流处理无论选择哪一种都可以回到 parse_exceptions.md 与 exceptions.md 获取异常编号与消息全文需要从零实现 SAX 处理器时sax_interface.md 给出了完整的回调清单与实现步骤。文中每个示例在仓库中都有对应的.cpp源文件与.output输出见 docs/mkdocs/docs/examples 下的 parse_error.cpp、parse__allow_exceptions.cpp、accept__string.cpp你可以直接用支持 C11 及以上的编译器配合单头文件single_include/nlohmann/json.hpp编译复现。【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表