` 元素访问:原理、用法与异常行为详解)
nlohmann/json 带越界检查的at()元素访问原理、用法与异常行为详解【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json导读在 C 中通过 JSON for Modern Cnlohmann/json读取与修改 JSON 对象或数组的成员时at()成员函数提供了带越界/键存在性检查的安全访问元素存在则返回其引用不存在则抛出basic_json::out_of_range异常。本篇文章以文档 checked_access.md 为主线结合 at.md 的完整接口说明与 json.hpp 源码实现全面讲解at()的对象键、数组下标、异构 KeyType如 C17 string_view以及 JSON Pointer 四种重载的用法、异常码、复杂度与 const 语义帮助你在要么拿到值、要么明确报错的严格场景下写出安全代码。at()是什么与operator[]相对的安全访问入口nlohmann::basic_json的元素访问方式主要有两类非检查访问operator[]当对象键不存在或数组下标越界时会隐式插入null或自动扩容数组使用灵活但可能静默改写数据结构检查访问at()目标元素存在就返回其引用否则直接抛出异常绝不隐式插入任何元素。正是由于这一差异at()是解析键/下标必须存在的外部数据时首选的安全 API。项目在元素访问这一主题下分别维护了三份姊妹文档checked_access.md本文主题、unchecked_access.mdoperator[]与 default_value.md带默认值的value并在 element_access/index.md 中对它们做了总览。三种访问方式的定位可以归纳为访问方式目标元素缺失时的行为典型用途operator[]自动插入null/ 扩容数组构造 JSON、就地追加at()抛出basic_json::out_of_range异常解析结构已知且必须完整的数据value(key, default)返回调用者提供的默认值可选字段读取四种函数签名从数组下标到 JSON Pointer完整的 API 声明记录在 at.md共四组重载每组都同时提供非 const 与 const 版本// (1) 数组元素下标访问带边界检查 reference at(size_type idx); const_reference at(size_type idx) const; // (2) 对象键访问带键存在性检查 reference at(const typename object_t::key_type key); const_reference at(const typename object_t::key_type key) const; // (3) 兼容异构键类型的对象键访问C17 起可传 string_view templatetypename KeyType reference at(KeyType key); templatetypename KeyType const_reference at(KeyType key) const; // (4) JSON Pointer 路径访问逐 token 检查 reference at(const json_pointer ptr); const_reference at(const json_pointer ptr) const;各重载要点如下重载 (1)idx为目标数组下标越界idx size()即抛out_of_range.401。重载 (2)key为对象键键不存在即抛out_of_range.403。重载 (3)为 (2) 的泛型版本自版本 3.11.0 引入要求KeyType能与string_t通过object_comparator_t透明比较最常见的用法是 C17 下直接传std::string_view作为键而无需构造临时std::string避免一次堆分配。重载 (4)自版本 2.0.0 引入通过 JSON Pointer 定位深层元素即便指针中每一段本身合法只要整体路径解析不通也会抛out_of_range.404。从源码结构看重载 (4) 在 json.hpp 中直接委托给json_pointer::get_checked实现与operator[]使用的get_unchecked形成一一对应的检查/非检查关系reference at(const json_pointer ptr) { return ptr.get_checked(this); }读访问示例逐一命中对象键与数组下标沿用 checked_access.md 中的经典示例假设已将下面的 JSON 解析为json变量j{ name: Mary Smith, age: 42, hobbies: [hiking, reading] }各at()表达式的返回值如下表所示表达式返回值j{name: Mary Smith, age: 42, hobbies: [hiking, reading]}j.at(name)Mary Smithj.at(age)42j.at(hobbies)[hiking, reading]j.at(hobbies).at(0)hikingj.at(hobbies).at(1)reading注意倒数两行展示了at()的可链式调用j.at(hobbies)先以对象键访问返回数组的引用紧接着对返回结果再次调用.at(0)/.at(1)以下标访问。每一次at()都独立完成一次检查任一环节失败都会立刻抛出异常。写访问示例引用即左值可直接回写原值at()的返回值是引用非 const 重载返回可变引用因此它既可用于读取也可直接用于修改原始 JSON无需再取一次指针j.at(name) John Smith;执行后j变为{ name: John Smith, age: 42, hobbies: [hiking, reading] }该写访问在示例集合 at__object_t_key_type.cpp 中有完整演示包含try/catch捕获type_error与out_of_range的写法其输出见 at__object_t_key_type.output。真实项目的常见配套写法是先find/contains判存在再at更新或用try/catch兜底。失效访问越界下标与不存在键的异常行为at()的检查语义决定了它在以下两种场景必然抛出异常对象键不存在find(key) end()→ 抛out_of_range.403数组下标越界idx size()→ 抛out_of_range.401。例如对j.at(hobbies)长度为 2 的数组执行下标 3 的写访问j.at(hobbies).at(3) cooking;不会如operator[]那样把数组扩容到 4 个元素而是直接抛出[json.exception.out_of_range.401] array index 3 is out of range这正是检查访问的核心价值失败即报错绝不静默改写数据从而把数据结构不符合预期这类错误尽早暴露出来。开启 JSON_DIAGNOSTICS 后的定位信息默认情况下异常消息只包含错误原因。当通过定义宏JSON_DIAGNOSTICS见 json_diagnostics.md开启扩展诊断消息后异常消息会额外携带一个 JSON Pointer直接指明缺失键或越界下标所在的路径[json.exception.out_of_range.401] (/hobbies) array index 3 is out of rangeJSON_DIAGNOSTICS可取1开启或0关闭默认值。需要权衡的是开启后每个 JSON 值都要额外保存一个指针并带来一定运行时开销详见 json_diagnostics.md。该宏在 3.11.0 起已把取值编码进命名空间无需全工程一致定义即可避免 ODR 违规。源码级原理at()的两条实现路径在 include/nlohmann/json.hpp 中数组/对象下标类at()的实现集中在第 2009~2127 行其检查逻辑清晰可分两类。数组下标版本json.hpp先通过is_array()判断类型再借用std::vector::at的边界检查把标准库抛出的std::out_of_range转换为带上下文消息的out_of_range.401reference at(size_type idx) { // at only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { JSON_TRY { return set_parent(m_data.m_value.array-at(idx)); } JSON_CATCH (std::out_of_range) { // create a better exception explanation JSON_THROW(out_of_range::create(401, detail::concat(array index , std::to_string(idx), is out of range), this)); } } else { JSON_THROW(type_error::create(304, detail::concat(cannot use at() with , type_name()), this)); } }对象键版本json.hpp先检查is_object()再对底层std::map执行find命中则返回it-second的引用未命中则抛出out_of_range.403reference at(const typename object_t::key_type key) { // at only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { JSON_THROW(type_error::create(304, detail::concat(cannot use at() with , type_name()), this)); } auto it m_data.m_value.object-find(key); if (it m_data.m_value.object-end()) { JSON_THROW(out_of_range::create(403, detail::concat(key , key, not found), this)); } return set_parent(it-second); }可归纳两条设计事实返回值是真正的引用函数通过it-second/array-at(idx)返回引用而非副本因此赋值的写访问、以及链式.at(...).at(...)才能成立异常类型与容器的底层行为解耦std::vector::at与std::map::find的差异被统一收敛为out_of_range.401/403两个异常码用户代码只需面向json::out_of_range一个异常类编程异常层级结构见 exceptions.md。非数组/非对象上的误用type_error.304文档 checked_access.md 与 at.md 一致强调at()只能用于对象传字符串键或数组传数值下标。对null、boolean、string、数值等其它 JSON 类型调用会抛出type_error.304消息形如[json.exception.type_error.304] cannot use at() with string对应源码即上文中type_error::create(304, ...)的分支且会在is_array()/is_object()判断的同一处非数组分支触发。测试 unit-element_access1.cpp 对null、boolean、string、object、各种数值类型逐一遍历验证了这一行为测试 unit-element_access2.cpp 则对用键访问非对象类型做了对称覆盖。JSON Pointer 访问及其异常矩阵重载 (4) 通过json_pointer实现深层定位其底层由json_pointer::get_checked实现于 json_pointer.hpp逐 reference token 处理遇到对象用at(token)检查键遇到数组先解析下标再做边界预检token-被显式判定为越界。相比前三种重载它引入的异常面更广at__json_pointer.output 中逐一展示了各类触发场景[json.exception.parse_error.106] parse error: array index 01 must not begin with 0 [json.exception.parse_error.109] parse error: array index one is not a number [json.exception.out_of_range.401] array index 4 is out of range [json.exception.out_of_range.402] array index - (2) is out of range [json.exception.out_of_range.403] key foo not found [json.exception.out_of_range.404] unresolved reference token foo对应 at.md 中声明的异常说明归纳如下异常码触发条件parse_error.106JSON Pointer 中的数组下标以0开头如01parse_error.109JSON Pointer 中的数组下标不是数字如oneout_of_range.401指针指向的数组下标越界out_of_range.402指针使用-作为数组下标at()不做插入故恒越界out_of_range.403指针指向的对象键不存在out_of_range.404指针整体无法解析如中途遇到标量out_of_range.410指针中的数组下标超出size_type可表示范围如 32 位平台异常安全与复杂度at.md 为at()声明了强异常安全保证一旦抛出异常原 JSON 值保持完整不变。这保证了先尝试at()再修改的惯用法在失败路径上是无副作用的。复杂度与底层容器直接相关均由 at.md 明确给出重载复杂度说明(1) 数组下标常数底层std::vector随机访问(2) 对象键容器大小的对数底层为有序关联容器键查找为O(log n)(3) 异构键容器大小的对数同 (2)(4) JSON Pointer容器大小的对数每段 token 一次检查访问const 语义对照表原文档在 Summary 中给出了非 const 值与 const 值即json与const json上的行为对照这里完整保留并整理为表格场景非 const 值const 值访问存在的对象键返回既有值的引用返回既有值的 const 引用访问合法的数组下标返回既有值的引用返回既有值的 const 引用访问不存在的对象键抛basic_json::out_of_range异常抛basic_json::out_of_range异常访问非法的数组下标抛basic_json::out_of_range异常抛basic_json::out_of_range异常对 const 值调用at()会匹配到const_reference at(...) const重载得到只读引用从编译期就杜绝了对数据的意外修改。这与operator[]在 const 对象上抛type_error.305见 unit-element_access1.cpp的行为不同——constjson下operator[]甚至不能安全使用而at()的 const 版本是只读但需校验场景的更佳选择。相应的 const 版可运行示例见 at__object_t_key_type_const.cpp、at__size_type_const.cpp。选型建议与版本演进结合三种访问语义实际选型可遵循如下经验法则结构必须存在、缺了就是错误如从外部 API 响应解析必备字段→ 用at()让缺失立刻以out_of_range.401/403暴露字段可缺省、缺了给默认值→ 用value(key, default)参考 default_value.md需要就地构建 / 允许自动补 null→ 才用operator[]参考 unchecked_access.md。异常捕获时at()的失败集中体现为两个异常类误用类型时是json::type_error304元素不存在时是json::out_of_range401/402/403/404/405/410两者都继承自统一的json::exception可用catch (const json::exception)一并处理其层级与完整消息定义见 exceptions.md。最后是at()各重载的版本演进史见 at.md对象键与数组下标两个重载自1.0.0起即存在JSON Pointer 重载于2.0.0加入异构KeyType模板重载于3.11.0加入同时引入透明比较与std::string_view支持。上述行为与当前仓库中 unit-element_access1.cpp 与 unit-element_access2.cpp 的测试断言一致可作为验证你理解是否正确的权威参照。【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考