十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

深入理解 nlohmann::json::size():JSON 值的元素个数语义、源码实现与使用陷阱

深入理解 nlohmann::json::size():JSON 值的元素个数语义、源码实现与使用陷阱 深入理解 nlohmann::json::size()JSON 值的元素个数语义、源码实现与使用陷阱【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/jsonsize() 是 nlohmann (JSON for Modern C) 中用于获取 JSON 值“元素个数”的核心容量接口。本文以 官方 API 文档 size.md 为主线结合仓库内头文件 include/nlohmann/json.hpp、官方示例 size.cpp 与单元测试 unit-capacity.cpp系统讲解其返回值规则、底层实现、复杂度保证以及字符串与二进制等最容易踩坑的语义区别帮助你写出类型安全、行为正确的 JSON 容量判断代码。接口签名与基本语义size()是nlohmann::basic_json类上的公有成员函数声明如下size_type size() const noexcept;其语义非常明确返回一个 JSON 值中“元素”的个数。其中size_type即无符号整数类型默认映射为std::size_t可通过模板参数SizeType定制。需要特别强调的是这里的“个数”并不是字节数、字符数或数组底层容量而是对 JSON 的七种值类型null、boolean、string、number、binary、object、array分别给出的统一定义。官方文档对这一函数归属的定位是 capacity 类接口与empty()、max_size()等构成一组互补的“容量查询”工具。按值类型划分的返回值规则官方文档给出了明确的返回值对照表这是理解size()的关键完整列举如下JSON 值类型size()返回值null0boolean1string1number1binary1object委托调用object_t::size()的结果即键值对个数array委托调用array_t::size()的结果即数组元素个数可见其设计思路非常清晰只有数组和对象是“容器型”值其余所有标量类型布尔、字符串、数字、二进制字节串都被视作一个整体单元其“元素个数”恒为1而null则代表“空值”个数为0。这里的object_t与array_t是basic_json的两个可定制模板容器类型。在默认配置下object_t基于有序/无序键控容器默认使用std::mapstd::string, basic_json, std::less之类的比较器定义见 include/nlohmann/json.hpp 中的类型别名详见 object_t 文档array_t默认是std::vectorbasic_json定义见 array_t 文档。因此size()对 object 和 array 的结果分别等于对象键值对个数与数组元素个数例如{one: 1, two: 2}返回2[1, 2, 4, 8, 16]返回5。当你通过basic_json模板参数自定义容器时返回值将自动跟随底层容器的size()语义。源码实现剖析一次针对 value_t 的 switch 分发size()的实现并非逐类型手工计算而是先读取当前 JSON 值内部的类型标记m_data.m_type枚举类型value_t再通过一次switch完成分发。相关实现位于 include/nlohmann/json.hpp#L3016-L3051关键代码如下size_type size() const noexcept { switch (m_data.m_type) { case value_t::null: { // null values are empty return 0; } case value_t::array: { // delegate call to array_t::size() return m_data.m_value.array-size(); } case value_t::object: { // delegate call to object_t::size() return m_data.m_value.object-size(); } case value_t::string: case value_t::boolean: case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: case value_t::binary: case value_t::discarded: default: { // all other types have size 1 return 1; } } }从源码可以提炼出几个有意思的实现事实统一存储、统一分发basic_json采用“类型标记 联合式存储”的经典设计m_type决定了当前解释m_valueunion中哪个成员size()只关心类型不关心具体数值内容。对象与数组是仅有的委托路径case value_t::array与case value_t::object直接把调用转发给底层容器从而天然支持自定义容器类型返回值、复杂度行为都与底层容器一致。number 内部三种变体一视同仁number_integer、number_unsigned、number_float在实现上虽然分属不同的内部存储成员但在size()语义上都归于“标量”统一返回1。内部类型discarded也返回 1value_t::discarded是一个仅供内部使用、表示“解析中被丢弃”的类型如json_lines场景下被过滤的片段。从文档表格看它不会出现但从实现看它同样落入“其余类型恒为 1”的分支。普通用户一般不会直接遇到可访问的 discarded 值属于实现细节。此外size()的实现与它的邻居们紧密呼应紧邻其上是empty()null 返回 true、标量与非空容器返回 false紧邻其下是max_size()见 include/nlohmann/json.hpp#L3055-L3085对数组与对象委托array_t::max_size()/object_t::max_size()对其余类型直接返回size()。三者共同构成一套完备的容量查询接口。最容易踩坑的语义陷阱string 与 binarysize()是新手最容易产生误解的接口之一官方文档特意用一节 Notes 强调size()并不返回字符串的字符或字节长度。当一个 JSON 值存放了字符串Hello, world时size()返回1因为该 JSON 值本身是“一个字符串元素”而不是字符串包含的 12 个字符。同理binary类型用于存放 CBOR、BSON 等二进制数据或 subtype 字节序列见 binary 相关文档在早期版本中曾被当作容器处理但从 3.8.0 起也统一为返回1——它同样被视为一个整体“二进制值”。那么想拿到字符串真实长度该怎么办正确做法是先把字符串取出来再调用底层string_t默认即std::string的size()例如// 拿到字符串的长度字节数 std::size_t len j_string.get_refconst json::string_t().size(); // 或借助 getstd::string 后再调用 std::size_t len2 j_string.getstd::string().size();若你的目标是“判断 JSON 值是否为空”对对象/数组指 0 个键值对/元素对 null 指空更推荐使用empty()而不是size() 0二者在标量上的语义相同但empty()在语义表达上更贴合意图。完整可运行示例与输出解读官方文档通过 size.cpp 展示了覆盖全部值类型的调用方式示例对九种 JSON 值逐一调用size()#include iostream #include nlohmann/json.hpp using json nlohmann::json; int main() { // create JSON values json j_null; json j_boolean true; json j_number_integer 17; json j_number_float 23.42; json j_object {{one, 1}, {two, 2}}; json j_object_empty(json::value_t::object); json j_array {1, 2, 4, 8, 16}; json j_array_empty(json::value_t::array); json j_string Hello, world; // call size() std::cout j_null.size() \n; std::cout j_boolean.size() \n; std::cout j_number_integer.size() \n; std::cout j_number_float.size() \n; std::cout j_object.size() \n; std::cout j_object_empty.size() \n; std::cout j_array.size() \n; std::cout j_array_empty.size() \n; std::cout j_string.size() \n; }运行输出如下与 size.output 完全一致0 1 1 1 2 0 5 0 1逐行解读这份输出可以帮你建立直觉输入值输出原因json j_null;null0null 视为空值trueboolean1标量恒为 117number_integer1标量恒为 123.42number_float1标量恒为 1{one:1,two:2}object2键值对个数空 objectvalue_t::object构造0无键值对[1,2,4,8,16]array5元素个数空 arrayvalue_t::array构造0无元素Hello, worldstring1标量恒为 1非字符数注意示例中使用json::value_t::object/json::value_t::array显式构造“空容器型值”这是区分“null 空值”与“空数组/空对象”的常用手法三者通过size()都得到0但它们的 JSON 类型语义截然不同序列化结果分别为null、{}、[]。该示例是 header-only 的只需包含 single_include/nlohmann/json.hpp或include/nlohmann/json.hpp以 C11 及以上标准编译即可运行无需额外链接库。复杂度与异常安全保证官方文档明确给出两条契约复杂度常量时间O(1)前提是array_t与object_t满足 C 标准中的Container概念——即它们自身的size()为常量复杂度。默认的std::vector与基于比较器的键控容器std::map语义都满足该要求因此在默认配置下可以放心地把size()用在循环条件、内存预分配计算等高频路径中。异常安全noexcept强保证no-throw guarantee函数自身永不抛异常。由于实现以const成员访问且对象与数组分支只是调用底层容器的size()而该调用对默认容器也不抛异常因此整个调用是安全的。需要补充一个模板相关的推论源自源码结构由于size()被声明为noexcept若用户自定义的object_t/array_t的size()声明为可能抛出异常调用时将在noexcept边界触发std::terminate风险默认容器类型不存在此问题。因此若在自定义容器场景下使用本接口应确保底层size()同样为无抛出实现。与迭代器的一致性测试如何验证语义在仓库单元测试中size()不仅验证“数值正确”还验证了它与迭代器的一致性契约——对一个容器的前向/反向迭代区间做std::distance的结果必须与size()相等。见 tests/src/unit-capacity.cpp 中的SECTION(size())用例例如对 boolean 值json j true; const json j_const true; CHECK(j.size() 1); CHECK(j_const.size() 1); // definition of size CHECK(std::distance(j.begin(), j.end()) j.size()); CHECK(std::distance(j_const.begin(), j_const.end()) j_const.size()); CHECK(std::distance(j.rbegin(), j.rend()) j.size()); CHECK(std::distance(j_const.crbegin(), j_const.crend()) j_const.size());测试同时覆盖 boolean、string、array、object 等类型以及const版本的对象验证了只读语义const对象同样可以安全调用size()标量可迭代即使 boolean/string 这样的标量也提供 begin/end 迭代器表现为单个元素因此size()为 1 与迭代区间长度为 1 严格一致反向迭代器一致rbegin()/rend()、crbegin()/crend()的区间长度同样等于size()。这一组测试表明size()是库对“JSON 值可迭代视图”的统一入口度量无论底层是数组、对象还是单个标量迭代遍历到的元素总数恰好等于size()。实践中这意味着你可以放心地用size()预估遍历代价、配合下标访问或at()检查边界。版本演进历史size()自1.0.0起加入该接口与库同源一直是 basic_json 的基础成员在3.8.0中扩展使binary类型字节容器 可选 subtype也返回1。在此之前二进制类型未被纳入统一的标量计数规则这一变更使 CBOR/BSON 等二进制值在容量语义上与 string 对齐避免了“二进制按字节数还是按单元数计数”的歧义。小结何时该用 size()何时不该需求场景推荐做法判断对象键值对个数 / 数组元素个数j.size()判断 JSON 值是否为 nullj.is_null()或j.type() json::value_t::null判断对象/数组是否为空j.empty()等价于j.is_null() || j.size() 0的可读替代获取字符串内字符/字节长度先get_refconst json::string_t()再取.size()而非直接j.size()获取二进制负载字节数通过j.get_binary()访问bytes.size()而非直接j.size()遍历容器时预估迭代总数j.size()与 begin/end 区间长度严格一致见测试证据一句话总结size()回答的是“这个 JSON 值有几个顶层元素”而不是“里面存了多少字符或字节”。牢牢把握“对象/数组看内部、其余标量恒为 1、null 恒为 0”这一规则就能在任何涉及 JSON 容量判断与遍历度量的代码中游刃有余。更深入的实现细节可继续阅读 basic_json 类的空判断实现、数组类型定制 与 对象类型定制 等相关 API 文档以及头文件中size()、empty()、max_size()三者的并列实现。【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表