ARTICLE DETAIL

资讯详情

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

JSON for Modern C++ 之 basic_json::operator==:相等性比较的完整语义与源码解析

JSON for Modern C++ 之 basic_json::operator==:相等性比较的完整语义与源码解析 JSON for Modern C 之 basic_json::operator相等性比较的完整语义与源码解析【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json本篇技术指南以 JSON for Modern Cnlohmann/json的官方 API 文档 docs/mkdocs/docs/api/basic_json/operator_eq.md 为骨架系统讲解basic_json相等性比较运算符的完整语义从函数签名、比较规则、NaN/null/discarded等特殊值行为到浮点数精度陷阱与不同basic_json特化json与ordered_json之间的差异并结合 include/nlohmann/json.hpp 的真实实现与 tests/src/unit-comparison.cpp 测试逐层剖析。读完你将掌握如何正确、可靠地在自己的 C 工程中比较 JSON 值并能在需要近似比较浮点数或自定义语义时写出安全的替代方案。函数签名与 C 版本差异// until C20 bool operator(const_reference lhs, const_reference rhs) noexcept; // (1) templatetypename ScalarType bool operator(const_reference lhs, const ScalarType rhs) noexcept; // (2) templatetypename ScalarType bool operator(ScalarType lhs, const const_reference rhs) noexcept; // (2) // since C20 class basic_json { bool operator(const_reference rhs) const noexcept; // (1) templatetypename ScalarType bool operator(ScalarType rhs) const noexcept; // (2) };两个重载的用途JSON 与 JSON 的比较比较两个 JSON 值是否相等。JSON 与标量的比较把标量转换为 JSON 值后再按规则 1 比较。该重载对任意满足std::is_scalarScalarType::value的类型生效C20 后约束为std::is_scalar_vScalarType例如整数、浮点数、布尔值、指针与nullptr。两个方向JSON标量、标量JSON都被支持。从源码看C20 分支通过 requires 约束见 include/nlohmann/json.hppC17 及更早版本则通过std::enable_if在模板参数上做 SFINAE 约束include/nlohmann/json.hpp。运算符从 free function 变为 member function 并不改变比较语义只是让重写规则rewritten candidates与联动更顺畅。需要指出在采用 three-way comparison的构建里operator!可由自动重写派生无需显式声明源码中仅在#else分支显式给出operator!定义见 include/nlohmann/json.hpp。比较规则operator重载 1按下列规则判定两个 JSON 值相等两个 JSON 值相等当且仅当(1) 两者都不是 discarded 值(2) 两者类型相同且其存储值按照各自底层容器/类型的operator判定相等。整数与浮点数在比较前会自动转换。例如17整数与17.0浮点数相等。类型相同时的底层比较当两侧类型一致时比较会被分派到对应底层存储上。源码中的JSON_IMPLEMENT_OPERATOR宏include/nlohmann/json.hpp用一个switch(lhs_type)覆盖全部value_t枚举value_t实际比较对象array底层array_t默认std::vectorbasic_json的operatorobject底层object_t默认std::map见下方特化说明的operatorstring底层string_t默认std::string的operatorboolean底层boolean_tbool的比较number_integer/number_unsigned/number_float对应数值类型的比较浮点默认是double即json::number_float_tbinary底层binary_tbyte_container_with_subtype的比较null恒定返回比较为真同一类型null彼此相等discarded/ 默认落入无序分支比较结果不成立跨数值类型的自动转换当两侧是不同数值类型时宏在进入switch之前会先做转换include/nlohmann/json.hppnumber_integervsnumber_float、number_unsignedvsnumber_float整数值被static_castnumber_float_t提升为浮点后比较number_unsignedvsnumber_integer及反向先检查number_integer一方是否为负。若为负则直接按不相等处理否则统一提升为number_unsigned后比较。这避免了无符号与有符号混比时的隐式转换陷阱。因此json(17) json(17.000000000000001L)、json(1u) json(1)这类字面不同、数值语义等价的表达式都会返回true而负整数永远不会被错误地当作巨大的无符号数。模板参数与函数参数ScalarType按std::is_scalarScalarType::valueC20 为std::is_scalar_vScalarType约束的标量类型包括算术类型、枚举、指针、成员指针与nullptr_t。lhs(in)参与比较的第一个 JSON 值。rhs(in)参与比较的第二个值JSON 值或将被转换为 JSON 值的标量。标量重载的实现本质为*this basic_json(rhs)或反向basic_json(lhs) rhs见 include/nlohmann/json.hpp。返回值、异常安全与复杂度返回值bool表示lhs/*this与rhs是否相等。异常安全无异常保证no-throw guaranteenoexcept声明此函数从不抛出异常。即便值内部是数组或对象其比较所依赖的容器比较操作也要求不抛异常GCC 下甚至显式忽略-Wfloat-equal警告见 include/nlohmann/json.hpp。复杂度线性Linear。最坏情况下需要比较容器中每个元素实际取决于两侧 JSON 的规模与形态。特殊值的比较语义重点注意事项文档对三类容易踩坑的特殊值给出了明确约定这在 include/nlohmann/json.hpp 的compares_unordered实现中有对应逻辑支撑1.NaN在数值域内是无序的以下比较全部返回false一个NaN与它自身比较一个NaN与另一个NaN比较一个NaN与任何其他数字比较。这是 IEEE 754 浮点语义的直接体现。compares_unordered会检测某一方是浮点NaN且另一方是任意数值类型的组合并提前判定为不相等。2. JSONnull值彼此全部相等。因为宏对value_t::null直接返回truenull_result参数所以json(nullptr)、json()等任何空值比较都是true。3.discarded值永远不与自己相等。discarded是解析sax/回调场景内部使用的占位类型宏为value_t::discarded返回unordered_result即false。换句话说json(json::value_t::discarded) json(json::value_t::discarded)结果是false——这是有意的设计使被丢弃的占位值永远不会参与相等匹配。测试 tests/src/unit-comparison.cpp 中构造了含discarded的笛卡尔积比较矩阵来验证这些行为。浮点数的比较精度与容差默认行为JSON 内的浮点数用json::number_float_t::operator默认即double::operator进行精确比较。这意味着0.1 0.2这类二进制浮点表示误差会导致预期外的false。文档给出了两种工程化应对方案一定义 epsilon 容差辅助函数templatetypename T, typename typename std::enable_ifstd::is_floating_pointT::value, T::type inline bool is_same(T a, T b, T epsilon std::numeric_limitsT::epsilon()) noexcept { return std::abs(a - b) epsilon; }std::numeric_limitsT::epsilon()是机器精度若你的业务误差更大可显式传入更大阈值如1e-6。方案二定义自定义 JSON 比较函数bool my_equal(const_reference lhs, const_reference rhs) { const auto lhs_type lhs.type(); const auto rhs_type rhs.type(); if (lhs_type rhs_type) { switch(lhs_type) { // 自定义情形 case value_t::number_float: return std::abs(lhs - rhs) std::numeric_limitsfloat::epsilon(); // 其余类型与原始 operator 保持一致 ... } } ... }要点当lhs_type rhs_type时只对number_float分支使用容差比较其他类型继续沿用类型匹配的精确语义类型不同时例如整数 vs 浮点仍应回退到官方运算符或自行决定转换策略。若需比较字符串/容器元素为浮点的情况可递归调用my_equal保证语义贯穿。不同 basic_json 特化的比较差异json vs ordered_json官方文档用一个现实案例提示比较结构内容相同、键顺序不同的 JSON 对象结果取决于使用的是nlohmann::json还是nlohmann::ordered_json。考虑如下两个对象{ version: 1, type: integer }与{ type: integer, version: 1 }对nlohmann::json默认object_t为std::map键有序而言两个对象相等结果为true对nlohmann::ordered_jsonobject_t为ordered_map保留插入顺序而言由于键序不同结果为false。完整可运行示例见 docs/mkdocs/docs/examples/operator__equal__specializations.cpp#include iostream #include iomanip #include nlohmann/json.hpp using json nlohmann::json; int main() { nlohmann::json uj1 {{version, 1}, {type, integer}}; nlohmann::json uj2 {{type, integer}, {version, 1}}; nlohmann::ordered_json oj1 {{version, 1}, {type, integer}}; nlohmann::ordered_json oj2 {{type, integer}, {version, 1}}; std::cout std::boolalpha (uj1 uj2) \n (oj1 oj2) std::endl; }输出对应 docs/mkdocs/docs/examples/operator__equal__specializations.outputtrue false结论判断对象相等性时务必明确你操作的是哪种特化。若你的业务不关心键顺序应优先使用默认json以获得与键序无关的相等判断若需要保留文档原始顺序且要求顺序敏感的比较则需留意ordered_json的这一语义。完整示例比较多种 JSON 类型下列示例覆盖数组、对象、数值含整浮转换与字符串的比较源文件为 docs/mkdocs/docs/examples/operator__equal.cpp#include iostream #include nlohmann/json.hpp using json nlohmann::json; int main() { // create several JSON values json array_1 {1, 2, 3}; json array_2 {1, 2, 4}; json object_1 {{A, a}, {B, b}}; json object_2 {{B, b}, {A, a}}; json number_1 17; json number_2 17.000000000000001L; json string_1 foo; json string_2 bar; // output values and comparisons std::cout std::boolalpha; std::cout array_1 array_2 (array_1 array_2) \n; std::cout object_1 object_2 (object_1 object_2) \n; std::cout number_1 number_2 (number_1 number_2) \n; std::cout string_1 string_2 (string_1 string_2) \n; }输出docs/mkdocs/docs/examples/operator__equal.output[1,2,3] [1,2,4] false {A:a,B:b} {A:a,B:b} true 17 17.0 true foo bar false逐行解读数组[1,2,3]与[1,2,4]长度相同但元素不同 →false对象{A:a,B:b}与{B:b,A:a}键序不同但在默认jsonstd::map存储下内容等价 →true整数17与浮点17.000000000000001L类型不同但跨类型自动转换后数值相等 →true字符串foo与bar内容不同 →false。完整示例与 nullptrJSON null比较源文件 docs/mkdocs/docs/examples/operator__equal__nullptr_t.cpp 演示各类 JSON 值与nullptr的比较——这是标量重载重载 2中最常用的场景之一等价于检查这个值是否为 JSON null#include iostream #include nlohmann/json.hpp using json nlohmann::json; int main() { // create several JSON values json array {1, 2, 3}; json object {{A, a}, {B, b}}; json number 17; json string foo; json null; // output values and comparisons std::cout std::boolalpha; std::cout array nullptr (array nullptr) \n; std::cout object nullptr (object nullptr) \n; std::cout number nullptr (number nullptr) \n; std::cout string nullptr (string nullptr) \n; std::cout null nullptr (null nullptr) \n; }输出docs/mkdocs/docs/examples/operator__equal__nullptr_t.output[1,2,3] nullptr false {A:a,B:b} nullptr false 17 nullptr false foo nullptr false null nullptr truenullptr被转换为 JSONnull后再参与比较因此只有同为 null 的值才返回true。注意它与is_null()成员函数判定的等价性j nullptr本质等同于j.is_null()。与相邻运算符的关系operator!不相等比较。在 C20 之前它由!(lhs rhs)显式定义include/nlohmann/json.hpp在 C20 之后可通过自动重写得到。operator!的语义是的严格逻辑取反。operatorC20 三路比较。与使用同一套JSON_IMPLEMENT_OPERATOR宏逻辑include/nlohmann/json.hpp因此NaN无序、跨数值类型自动转换等规则在两者间保持一致。版本历史重载 1 于1.0.0加入3.11.0起在 C20 下提供成员函数形式。重载 2 于1.0.0加入3.11.0起在 C20 下提供成员函数形式。使用建议与注意事项汇总默认精确比较operator对浮点执行精确比较涉及浮点运算结果的相等性判断前先考虑精度问题必要时采用 epsilon 方案。NaN永不相等即使两侧是同一个NaN比较也返回false不要把当作判断是否存在 NaN的手段。区分json与ordered_json需要键序无关的语义用json需要顺序敏感语义用ordered_json并接受其相同内容不同键序不相等的结果。discarded不参与任何相等匹配这保证了解析回调等内部占位不会污染业务数据比较。复杂度是线性的对超大数组/对象做频繁相等性比较时可先比较类型与size()做快速失败短路再进入元素级比较。以上结论均已由 include/nlohmann/json.hpp 中的宏实现与compares_unordered逻辑、tests/src/unit-comparison.cpp 的比较矩阵测试以及文档自带示例 docs/mkdocs/docs/examples 交叉验证可作为工程决策的可靠依据。【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表