nlohmann-json 详解:basic_json::at 带边界检查的元素访问(数组、对象与 JSON Pointer 四重载)

nlohmann-json 详解:basic_json::at 带边界检查的元素访问(数组、对象与 JSON Pointer 四重载) nlohmann-json 详解basic_json::at 带边界检查的元素访问数组、对象与 JSON Pointer 四重载【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json本文围绕 nlohmann-jsonJSON for Modern C库中nlohmann::basic_json::at成员函数的 API 文档展开完整讲解其 4 个重载的签名、参数、返回值、异常语义与复杂度并逐一对应仓库中的可运行示例与预期输出。同时结合include/nlohmann/json.hpp与include/nlohmann/detail/json_pointer.hpp中的源码实现剖析at如何与operator[]无检查访问区分、以及 JSON Pointer 重载内部get_checked的异常抛出点。读完后你可以在 C 项目中安全地按下标、按键名、按string_view、按 JSON Pointer 读写 JSON 值并正确捕获和处理type_error、out_of_range、parse_error异常。at 的四个重载签名at是basic_json的“带边界检查的元素访问”接口当目标元素存在时返回其引用否则抛出异常。根据官方 API 文档docs/mkdocs/docs/api/basic_json/at.md它提供 4 组重载非 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) 模板化键类型可与 string_t 比较的类型如 C17 的 string_view templatetypename KeyType reference at(KeyType key); templatetypename KeyType const_reference at(KeyType key) const; // (4) 按 JSON Pointer 访问 reference at(const json_pointer ptr); const_reference at(const json_pointer ptr) const;四个重载的语义分别是返回数组中下标idx处的元素引用带边界检查返回对象中键key对应的元素引用带键存在性检查同 (2)但该重载仅在KeyType可与object_t::key_type用object_comparator_t比较、且object_comparator_t::is_transparent表示一个类型时可用——这正是 C17std::string_view免拷贝查找的前提返回 JSON Pointerptr所指向的元素引用带全程检查。模板参数与函数参数参数含义KeyType模板参数重载 3对象键的类型可与string_t用object_comparator_t比较也可以是字符串视图C17idx输入要访问的数组元素下标key输入要访问的对象元素键ptr输入指向目标元素的 JSON Pointer返回值统一为“对应位置的引用”const 版本返回const_reference因此可以直接用来修改原 JSON 值例如j.at(name) John;。异常安全保证文档明确给出强异常安全strong exception safety语义如果抛出异常原值保持完整不变。这一点很重要——at是“检查式”访问绝不会像operator[]那样隐式插入或转换值类型null 转 array/object 等所以任何失败路径上 JSON 数据都不会被改动。复杂度重载复杂度(1) 数组下标常数(2) 对象键容器大小的对数(3) 模板键容器大小的对数(4) JSON Pointer容器大小的对数异常一览type_error、out_of_range、parse_error各重载可能抛出的异常是atAPI 最核心的契约完整继承自文档重载 (1)数组下标type_error.304JSON 值不是数组时抛出对非数组类型用下标调用at没有意义out_of_range.401下标越界即idx size()。重载 (2)对象键type_error.304JSON 值不是对象时抛出out_of_range.403键不存在即find(key) end()。重载 (3)模板键异常同重载 (2)。重载 (4)JSON Pointerparse_error.106JSON Pointer 中数组索引以0开头如/array/01parse_error.109数组索引不是数字如/array/oneout_of_range.401数组索引越界out_of_range.402使用了数组索引-。因为at是带检查访问、不会隐式插入元素-JSON Pointer 中表示“追加到数组末尾”的追加索引在这里永远非法out_of_range.403JSON Pointer 描述的某个对象键找不到out_of_range.404JSON Pointer 无法解析某引用令牌在对应非数组/非对象节点上无法前进out_of_range.410数组索引超出size_type的表示范围例如在 32 位平台上。异常语义与 checked access 专题文档一致该文档给出了一张“场景 × 非常量/常量”的总表——访问已存在的键/合法下标返回引用const 值返回const引用访问不存在的键/非法下标则一律抛出basic_json::out_of_range。示例 (1)按数组下标读写与异常演示仓库示例 at__size_type.cpp 演示了数组元素的读、写以及两类异常#include iostream #include nlohmann/json.hpp using json nlohmann::json; int main() { // create JSON array json array {first, 2nd, third, fourth}; // output element at index 2 (third element) std::cout array.at(2) \n; // change element at index 1 (second element) to second array.at(1) second; // output changed array std::cout array \n; // exception type_error.304 try { // use at() on a non-array type json str I am a string; str.at(0) Another string; } catch (const json::type_error e) { std::cout e.what() \n; } // exception out_of_range.401 try { // try to write beyond the array limit array.at(5) sixth; } catch (const json::out_of_range e) { std::cout e.what() \n; } }对应的 预期输出可复制到终端比对运行结果third [first,second,third,fourth] [json.exception.type_error.304] cannot use at() with string [json.exception.out_of_range.401] array index 5 is out of rangeconst 版本 at__size_type_const.cpp 在只读访问下演示相同的异常对const值调用at返回const_reference写操作不可用third [json.exception.type_error.304] cannot use at() with string [json.exception.out_of_range.401] array index 5 is out of range源码剖析数组重载如何转换底层异常从 json.hpp 的实现 看非 const 的数组重载逻辑非常直接reference 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)); } }可以推断出两个实现要点它把标准库std::vector::at抛出的裸std::out_of_range捕获并转换为库自己的nlohmann::json::out_of_range错误码 401这样用户只需捕获一种异常类型体系返回前调用set_parent(...)把子节点与其父 JSON 的关联写入用于诊断信息与父指针维护。这正是异常安全承诺的体现转换发生在返回引用之前失败时不触碰数据。注意源码用了JSON_HEDLEY_LIKELY标注“是数组”分支——库把类型正确视为热路径而对象重载用JSON_HEDLEY_UNLIKELY(!is_object())体现了对常见正确用法的分支预测优化。示例 (2)按对象键读写与异常演示示例 at__object_t_key_type.cpp 演示按键读写的行为#include iostream #include nlohmann/json.hpp using json nlohmann::json; int main() { // create JSON object json object { {the good, il buono}, {the bad, il cattivo}, {the ugly, il brutto} }; // output element with key the ugly std::cout object.at(the ugly) \n; // change element with key the bad object.at(the bad) il cattivo; // output changed array std::cout object \n; // exception type_error.304 try { // use at() on a non-object type json str I am a string; str.at(the good) Another string; } catch (const json::type_error e) { std::cout e.what() \n; } // exception out_of_range.401 try { // try to write at a nonexisting key object.at(the fast) il rapido; } catch (const json::out_of_range e) { std::cout e.what() \n; } }预期输出il brutto {the bad:il cattivo,the good:il buono,the ugly:il brutto} [json.exception.type_error.304] cannot use at() with string [json.exception.out_of_range.403] key the fast not found注意输出对象中键的顺序good/bad/ugly 变为 bad/good/ugly默认object_t为std::map按字典序排序这也是文档中“对象访问复杂度为对数”的原因。const 版本 at__object_t_key_type_const.cpp 输出il brutto [json.exception.type_error.304] cannot use at() with string [json.exception.out_of_range.403] key the fast not found源码剖析对象重载先 find 后返回对象重载的实现见 json.hppreference 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); }与数组重载不同这里没有依赖底层容器的at而是显式find后判end()再抛出 403。这也解释了文档中“out_of_range.403当且仅当find(key) end()”的精确表述。模板键重载重载 3的实现与之几乎相同区别仅在于用std::forwardKeyType(key)转发实参并通过 SFINAE 约束templateclass KeyType, detail::enable_if_t detail::is_usable_as_basic_json_key_typebasic_json_t, KeyType::value, int 0 reference at(KeyType key)只有满足is_usable_as_basic_json_key_type的类型即可与string_t透明比较的键类型才能参与重载决议——这就是文档所说“仅当object_comparator_t::is_transparent表示一个类型时可用”的编译期实现。示例 (3)C17 string_view 键的透明比较重载 (3) 是 3.11.0 版本新增的。示例 at__keytype.c17.cpp 展示了用std::string_view字面量作为键避免为每次查找临时构造std::string#include iostream #include string_view #include nlohmann/json.hpp using namespace std::string_view_literals; using json nlohmann::json; int main() { // create JSON object json object { {the good, il buono}, {the bad, il cattivo}, {the ugly, il brutto} }; // output element with key the ugly using string_view std::cout object.at(the uglysv) \n; // change element with key the bad using string_view object.at(the badsv) il cattivo; // output changed array std::cout object \n; // exception type_error.304 try { // use at() with string_view on a non-object type json str I am a string; str.at(the goodsv) Another string; } catch (const json::type_error e) { std::cout e.what() \n; } // exception out_of_range.401 try { // try to write at a nonexisting key using string_view object.at(the fastsv) il rapido; } catch (const json::out_of_range e) { std::cout e.what() \n; } }预期输出 中有一个值得注意的细节il brutto {the bad:il cattivo,the good:il buono,the ugly:il brutto} [json.exception.type_error.304] cannot use at() with string out of range最后一条输出是out of range而非[json.exception.out_of_range.403] ...。原因见源码模板重载在键未找到时先执行string_t(std::forwardKeyType(key))把string_view构造成string再拼消息而std::string从空视图/非法构造路径可能抛出std::out_of_range被std::runtime_error的what()打印为out of range先于库自身的 403 异常。换言之string_view路径下异常信息可能与std::string路径略有差异这是实现层面的事实使用时以实际捕获类型为准。const 版本 at__keytype_const.c17.cpp 的行为相同输出为il brutto [json.exception.type_error.304] cannot use at() with string out of range适用前提C17 或更新标准std::string_view且底层对象比较器支持透明比较默认的std::map比较器满足。示例 (4)按 JSON Pointer 的带检查访问JSON Pointer 重载把“逐层导航”封装成一次调用。示例 at__json_pointer.cpp 覆盖了读取、写入和文档列出的全部异常分支#include iostream #include nlohmann/json.hpp using json nlohmann::json; using namespace nlohmann::literals; int main() { // create a JSON value json j { {number, 1}, {string, foo}, {array, {1, 2}} }; // read-only access // output element with JSON pointer /number std::cout j.at(/number_json_pointer) \n; // output element with JSON pointer /string std::cout j.at(/string_json_pointer) \n; // output element with JSON pointer /array std::cout j.at(/array_json_pointer) \n; // output element with JSON pointer /array/1 std::cout j.at(/array/1_json_pointer) \n; // writing access // change the string j.at(/string_json_pointer) bar; // output the changed string std::cout j[string] \n; // change an array element j.at(/array/1_json_pointer) 21; // output the changed array std::cout j[array] \n; // out_of_range.106 try { // try to use an array index with leading 0 json::reference ref j.at(/array/01_json_pointer); } catch (const json::parse_error e) { std::cout e.what() \n; } // out_of_range.109 try { // try to use an array index that is not a number json::reference ref j.at(/array/one_json_pointer); } catch (const json::parse_error e) { std::cout e.what() \n; } // out_of_range.401 try { // try to use an invalid array index json::reference ref j.at(/array/4_json_pointer); } catch (const json::out_of_range e) { std::cout e.what() \n; } // out_of_range.402 try { // try to use the array index - json::reference ref j.at(/array/-_json_pointer); } catch (const json::out_of_range e) { std::cout e.what() \n; } // out_of_range.403 try { // try to use a JSON pointer to a nonexistent object key json::const_reference ref j.at(/foo_json_pointer); } catch (const json::out_of_range e) { std::cout e.what() \n; } // out_of_range.404 try { // try to use a JSON pointer that cannot be resolved json::reference ref j.at(/number/foo_json_pointer); } catch (const json::out_of_range e) { std::cout e.what() \n; } }预期输出 完整列出了每类异常的信息文本1 foo [1,2] 2 bar [1,21] [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其中out_of_range.402的消息array index - (2) is out of range里的(2)是当时的数组长度——源码在拼消息时把size()一并提供方便定位。const 版本 at__json_pointer_const.cpp 将写访问改为只读后演示相同异常其输出at__json_pointer_const.output前四行为1 foo [1,2] 2后接与上相同的六条异常信息。源码剖析at(ptr) 只是 get_checked 的薄封装basic_json::at(const json_pointer)的实现只有一行见 json.hppreference at(const json_pointer ptr) { return ptr.get_checked(this); }真正的逻辑在json_pointer::get_checked中见 json_pointer.hpp 附近它对每个引用令牌逐个处理对象键查不到抛 403、数组下标解析失败抛 401/402指针无法在剩余令牌上继续前进时抛 404unresolved reference token ...而 410 在 引用令牌转换阶段 抛出。此外parse_error.106/109来自指针解析json_pointer构造/operator/解析引用令牌时对“前导零”和“非数字数组索引”的校验——这也是为什么示例注释把 106/109 误标为out_of_range但实际抛出的是parse_error捕获json::parse_error才能命中。源码中get_checked与get_unchecked成对存在后者是operator[]使用的、不检查的路径二者对照即可看清at与operator[]的本质区别operator[]无检查且对非对象/非数组值可能隐式转换或追加无副作用承诺at全程检查绝不隐式插入失败即抛异常且原值不变。与 operator[] 和 value 的对比、版本历史官方文档“See also”部分给出三个延伸阅读入口checked access 专题系统说明at的读/写语义、无效下标与缺失键的行为以及定义JSON_DIAGNOSTICS后异常信息会附带出错的 JSON Pointer 位置例如[json.exception.out_of_range.401] (/hobbies) array index 3 is out of rangeoperator[]无检查的引用访问value带默认值的访问键缺失时返回默认值而不抛异常。版本历史继承自文档重载引入版本(1) 数组下标1.0.0(2) 对象键1.0.0(3) 模板键string_view 等3.11.0(4) JSON Pointer2.0.0实战建议与验证方式优先用at做防御式访问对来自外部输入HTTP 响应、配置文件等的 JSON用at 异常捕获比operator[]更能精确区分“值不存在”out_of_range与“类型不对”type_error若允许缺省值则用value(key, default)。按异常码分支处理捕获json::out_of_range后可读取e.id如 401/402/403/404/410做细粒度处理parse_error106/109单独捕获。验证方式仓库的示例源码均位于 docs/mkdocs/docs/examples/每个.cpp旁都有同名.output预期输出文件如 at__json_pointer.output可以复制示例编译运行后与输出逐行比对更完整的回归测试见 tests/src/unit-element_access1.cpp 与 tests/src/unit-json_pointer.cpp。注意标准版本约束重载 (3) 需要 C17string_viewJSON Pointer 字面量_json_pointer需using namespace nlohmann::literals;。【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考