mirror of
https://github.com/nlohmann/json.git
synced 2026-10-02 20:50:32 +00:00
* Drop stale LCOV_EXCL_LINE from the json_pointer out_of_range.410 throw The comment said the size_type overflow check in array_index() is only triggered on special platforms like 32-bit, and the throw was excluded from coverage. On 64-bit platforms the check is true for SIZE_MAX itself, and unit-json_pointer.cpp has asserted that case four times since #5395, so the line is executed in the coverage job. Reword the comment and remove the exclusion marker so the coverage report notices if the tests stop reaching it. Part of #5725 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Name all three C-array check aliases in the enum-macro NOLINTs NLOHMANN_JSON_SERIALIZE_ENUM(_STRICT) suppressed the c-array warning under modernize-avoid-c-arrays only, but clang-tidy emits the same diagnostic under the aliases cppcoreguidelines-avoid-c-arrays and hicpp-avoid-c-arrays too. Any user running those checks got a false positive at every macro expansion, and our own tests needed a local NOLINT at each call site to work around it. Name all three aliases in the four macro comments instead, and drop the now-redundant c-array names from the five test call-site NOLINTs. Comment-only change; behavior, the public API, and the ABI do not change. Ran make amalgamate. Part of #5725 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Include doctest as a SYSTEM directory instead of disabling warnings for all tests test_main added -Wno-deprecated and -Wno-float-equal as PUBLIC compile options for every non-MSVC compiler, so they were applied to every translation unit, library headers included, and silenced the CI warnings meant to check the library's own -Wfloat-equal pragmas. The only code that actually needed the suppression was the vendored doctest.h, which was included as a normal (non-SYSTEM) directory. Include thirdparty/doctest as SYSTEM for test_main, matching what tests/abi/CMakeLists.txt already does, and drop the two suppressions from both targets. Verified locally that unit-comparison, unit-conversions and unit-constructor1 compile clean with -Werror -Weverything and doctest as -isystem, and that CMake still configures with JSON_BuildTests=ON. Part of #5725 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Remove the no-op ci_clang_analyze target ci_clang_analyze configured the build with the real compiler and only then wrapped ninja with scan-build. scan-build intercepts compiles by overriding CC/CXX, but build.ninja already had the compiler path baked in from the configure step, so every run bypassed the analyzer: CI logs show "No bugs found" after a normal build, never an analysis. The job also used Debian's frozen clang-tools-14 rather than the image's own clang, and CLANG_ANALYZER_CHECKS still named three valist.* checkers that current clang merged into security.VAList. ci_clang_tidy already runs every clang-analyzer-* check (via .clang-tidy's "Checks: '*'") with warnings as errors, so nothing is lost by removing the dead job. Delete ci_clang_analyze, CLANG_ANALYZER_CHECKS and the SCAN_BUILD_TOOL lookup from cmake/ci.cmake, drop it from the ubuntu.yml ci_static_analysis_clang matrix, and drop the now-unused clang-tools apt package (iwyu stays for ci_single_binaries). Reword quality_assurance.md and assurance_case.md, which described the dead job as a working control, to say the Clang Static Analyzer checks run through clang-tidy. Verified that `cmake -DJSON_CI=ON` still configures cleanly and that ci_clang_analyze no longer appears in the generated build or in any CMake/workflow file. Part of #5725 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Re-enable portability-template-virtual-member-function; remove redundant forwards .clang-tidy disabled three checks "to get the CI going" (#4489, 2024-11-13): portability-template-virtual-member-function, bugprone-use-after-move and its alias hicpp-invalid-access-moved. portability-template-virtual-member-function only flagged output_stream_adapter::write_character/write_characters; annotate both with NOLINT and re-enable the check. bugprone-use-after-move flagged several double forwards that have no effect at runtime: - from_json.hpp calls std::forward<BasicJsonType>(j).at(Idx) inside pack expansions; at() has no ref-qualified overloads and always returns an lvalue reference, so the forward is a no-op. Replace with plain j.at(Idx) in all four places. - the move constructor forwards the whole object to its base class and then reads other's members. That is item 9 of #5724 (together with its cppcheck suppressions) and is left to that change. - input_adapters.hpp forwards the container twice on purpose, so the begin/end iterator types match adapter_type; annotate with NOLINT and a comment instead of changing behavior. The check still flags the move constructor (see above) and two sites in at(KeyType&&) (both overloads, json.hpp, in the throw's string_t(std::forward<KeyType>(key)) after find(std::forward<KeyType>(key))). Open PR #5689 rewrites that hunk, so bugprone-use-after-move (and hicpp-invalid-access-moved) stay disabled for now, with a comment explaining why; re-enable them once #5689 and the #5724 move-constructor change have landed. Also resolve the portability-avoid-pragma-once TODO: single_include never has #pragma once (amalgamate.py strips it) and every supported compiler accepts it in include/, so keep it disabled with an explanatory comment instead of a TODO. Fix the stale "json.hpp, around line 1265" comment in unit-class_parser.cpp, which now points at the move constructor's actual line. Behavior, the public API and the ABI do not change. Verified with clang-tidy 22.1.8 that portability-template-virtual-member-function now reports nothing, that bugprone-use-after-move/ hicpp-invalid-access-moved report only the known at(KeyType&&) and move-constructor sites, and that unit-custom-base-class, unit-constructor1, unit-conversions, unit-element_access2, unit-class_parser and unit-diagnostic-positions (JSON_DIAGNOSTIC_POSITIONS=1) compile under ASan/UBSan and pass with the same assertion counts as before. Ran make amalgamate. Part of #5725 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix stale and malformed NOLINT comments json_sax.hpp named "-warnings-as-errors" in the NOLINT list on the two JSON_ASSERT(false) lines; that is the suffix clang-tidy appends to a diagnostic tag under WarningsAsErrors, not a check name, and every other JSON_ASSERT(false) omits it. unit-capacity.cpp carried 30 "// NOLINT(misc-const-correctness)" comments on "json j = ...;" declarations that are all used with non-const members afterwards, so the check has nothing to report there. unit-constructor2.cpp used a blanket "// NOLINT: access after move is OK here" on a use-after-move that hides every check on the line; naming bugprone-use-after-move and hicpp-invalid-access-moved keeps the intent once those checks are re-enabled (#5724). Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5725 item 10 * Remove stale .clang-tidy entries -google-runtime-references disabled a check that neither clang-tidy 22.1.8 nor 23.1.2 lists under --list-checks -checks='*'; it was removed upstream. The commented-out HeaderFilterRegex line has been unused since the active HeaderFilterRegex was introduced in #2561 (2021). Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5725 item 11 * Remove the GCC C++20 -Wignored-attributes pragma in json.hpp The pragma (added in #5164) claimed to work around the C++ modules redefinition errors of #5103, but #5103 is about hard errors (e.g. "redefinition of std::__is_constant_evaluated()", conflicting std::integral_constant) that ignoring a warning cannot suppress; they are traced to GCC PR 124430 and reproduce with <map> or <string> instead of json.hpp too. A GCC 16.2 -std=gnu++20 -fmodules build following #5103's repro steps still fails with the pragma in place, and a build of all test TUs with GCC_CXXFLAGS (which enable -Wignored-attributes) and the pragma removed produces no such warning. The block only hid a warning class from GCC C++20 users while suggesting #5103 was handled. Overlaps #5610, whose hunks touch the closing half of this pragma to insert the json_literals.hpp include. Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5725 item 9 * Fix stale doxygen comments hidden by the -Wdocumentation pragma macro_scope.hpp ignores -Wdocumentation and -Wdocumentation-unknown-command for the whole library, which also hides genuine documentation mistakes: - detail::unescape() documented "@return unescaped string" but returns void and unescapes its argument in place; reworded to "@param[in,out] s string to unescape in place" and dropped the bogus @return. - basic_json::get()'s copy-conversion overload wrote "converted to @tparam ValueType" inside @return, which Doxygen and Clang parse as a second, malformed @tparam; changed to "@a ValueType", matching the two other get() overloads a few lines above that already use it. This narrows the gap the -Wdocumentation pragma needs to cover; fully replacing the Doxygen-only commands it also hides (item 2c) is left for after #5267. Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5725 item 2 * Fix -Wextra-semi-stmt at its actual source, not assert() clang_flags.cmake blamed the global -Wno-extra-semi-stmt on assert(), but assert() expands to an expression under glibc and libc++ and does not trigger this warning. unit-assert_macro.cpp overrides JSON_ASSERT with "{if (!(x)) ++assert_counter; }", a bare block followed by a semicolon at every JSON_ASSERT(...) call site in the library; that was the actual source of 151 of the 208 -Wextra-semi-stmt sites found in a Clang 22 -Weverything sweep of the test suite with the flag removed. Switched to the standard do/while(false) macro idiom, which does not expand to a statement-plus-semicolon, and corrected the comment to name the remaining source instead: vendored Doctest's CAPTURE(x) shim, which already ends in a semicolon. Verified with clang++ -Wextra-semi-stmt (plus the file's other CI ignores) that unit-assert_macro.cpp now compiles without any -Wextra-semi-stmt diagnostic. Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5725 item 8 (step 1 of 2; step 2 covers the CAPTURE() call sites) * Drop the redundant semicolon from CAPTURE() call sites; remove -Wno-extra-semi-stmt doctest_compatibility.h defines CAPTURE(x) as DOCTEST_CAPTURE(x); (with a trailing semicolon baked into the macro), specifically so call sites do not need to add one themselves; most of the ~267 call sites already follow that convention. The remaining 64 call sites across 20 files wrote "CAPTURE(x);" anyway, turning into a statement plus an empty statement and triggering -Wextra-semi-stmt. Dropped the redundant semicolon at each of those sites. With item 6 having already made vendored Doctest a SYSTEM include, and this the last known source of -Wextra-semi-stmt findings, removed the flag from clang_flags.cmake entirely. Verified with clang++ -Wextra-semi-stmt (plus the file's other CI ignores) that all 20 touched files, plus a file with no CAPTURE() use (unit-json_pointer.cpp), compile without any -Wextra-semi-stmt diagnostic. Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5725 item 8 (step 2 of 2) * Switch ci_static_analysis_clang off the frozen LLVM 22 dev image ubuntu.yml pinned the clang-tidy/clang-tidy-sanitizer/single-binaries job to silkeh/clang:dev, a tag last pushed 2026-02-18 that reports "clang version 22.0.0 (...+20251015...)", a pre-release snapshot from before the LLVM 22 release; the maintainer now updates dev-unstable, 22, and latest instead. Switched to silkeh/clang:22, matching the other clang jobs on :latest. Verified with clang-tidy 22.1.8 (the image's actual version) against this repository's .clang-tidy and library headers what the release image newly reports compared to :dev: - readability-redundant-typename fires at ~250 sites across the _cpp20-relevant conversion/to_chars headers; the library targets C++11 and keeps the typenames, so the check is disabled in .clang-tidy, matching how the file already handles checks that don't fit a C++11 codebase. - misc-anonymous-namespace-in-header fires on the two anonymous namespaces in from_json.hpp and to_json.hpp; added the alias to their existing NOLINT (cert-dcl59-cpp, fuchsia-header-anon-namespaces, google-build-namespaces). - bugprone-std-namespace-modification fires on every addition to namespace std: the std::hash, std::formatter and std::swap overloads in json.hpp, and the std::tuple_size/std::tuple_element specializations in iteration_proxy.hpp (this last file is not named in #5725's item 5, found by actually running clang-tidy 22.1.8 against the current tree). All six are legal, deliberate additions to namespace std (explicit/partial specializations of std types, or the pre-C++20 std::swap overload); annotated each with the check name next to its existing cert-dcl58-cpp NOLINT. - modernize-avoid-c-style-cast reported nothing new. Also added clang++-22/21, clang-tidy-22/21, g++-16 and gcov-16 to the find_program search lists in ci.cmake so a local "maximal warnings" configure prefers the current toolchain version over an older one on PATH. #5725 item 5 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Regenerate cmake/gcc_flags.cmake for GCC 16.2.0 GCC_CXXFLAGS was generated for GCC 15.1.0, but ci_test_gcc and ci_test_gcc_cxx{11..26} now run in gcc:latest, currently GCC 16.2.0, so the "maximal warnings" job was missing warnings introduced since 15.1.0 while carrying entries GCC 16 treats as duplicates or no-ops. Regenerated with https://github.com/nlohmann/gcc_flags (patched locally to not crash on an option whose "-x c++ <opt> -" probe fails before it reads stdin, e.g. -Wabi=; the tool otherwise raises BrokenPipeError instead of recording the option as an error) run against g++ 16.2.0 in the official gcc:16 Docker image, keeping the documented -Wno-* exclusions and the same alphabetical placement scheme as before. Also added three GCC 16 warnings the generator cannot discover on its own because it only probes value ranges/lists it finds in the -Q option name itself, not in the enum choices --help=warnings documents separately: - -Wbidi-chars=any, -Wleading-whitespace=spaces: manually verified these compile cleanly with g++ 16.2.0. - -Wstrict-flex-arrays: deliberately NOT added, unlike the other two. Without -fstrict-flex-arrays (which the library does not enable, as it would change codegen for flexible array members), GCC prints "'-Wstrict-flex-arrays' is ignored when '-fstrict-flex-arrays' is not present" on every translation unit, and under our -Werror that note itself aborts the build. This differs from the harmless no-op warnings already kept in the file (-Whsa, -Wsynth, -Wunreachable-code, -Wunsafe-loop-optimizations), which emit nothing; #5725 item 7 named -Wstrict-flex-arrays as one of the flags GCC 16 adds, but did not anticipate this failure mode. Verified: compiled the library header and a representative set of test translation units (including ones touched by items 1, 3, 8, 9, 10 of this issue) with the regenerated GCC_CXXFLAGS plus -Werror under g++ 16.2.0 at -std=c++11 through -std=c++26, with zero warnings; ran the full local test suite (129/129 passing, unrelated to this compiler) as a regression check. CI must still confirm the actual ci_test_gcc / ci_test_standards_gcc targets end to end, since this was verified with direct g++ invocations rather than through the CMake/ CXXFLAGS environment-variable plumbing in ci.cmake. #5725 item 7 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Avoid std::basic_string<CharType> for non-character output_adapter CharType output_adapter<CharType, StringType> defaulted StringType to std::basic_string<CharType>, and (with JSON_NO_IO undefined) always declared a std::basic_ostream<CharType>&-taking constructor. For CharType with no non-deprecated std::char_traits specialization (only std::uint8_t is ever used this way, by the binary writers), simply naming either type - as an unused default template argument, or as an unused, never-called constructor's parameter type - instantiates std::char_traits<CharType> merely to name it, which some standard libraries mark deprecated: with the library-wide -Wdocumentation pragma (item 2's other half, left for a later commit) temporarily removed, an Apple clang 21 / libc++ TU calling json::to_cbor(j, vec) with std::vector<std::uint8_t>& got one -Wdeprecated-declarations warning per binary writer at the old output_adapters.hpp:193. Replaced the eager std::basic_string<CharType> / std::basic_ostream <CharType> defaults with a bool-tagged partial specialization (not std::conditional, which requires naming both branches' types up front regardless of which is selected, reproducing the same warning) that only ever names std::basic_string<CharType> / std::basic_ostream <CharType> when CharType is actually one of char, wchar_t, char16_t, char32_t, or (with __cpp_lib_char8_t) char8_t. For any other CharType, output_adapter's StringType and ostream-constructor parameter fall back to two distinct empty placeholder types, kept distinct so the two constructor overloads do not collide into a single redeclaration. Public API / behavior: passing a std::basic_string<std::uint8_t>& or std::basic_ostream<std::uint8_t>& directly to a binary writer's output_adapter now fails to compile instead of compiling with a deprecation warning; this was neither documented nor tested. All documented uses (std::vector<CharType>, std::basic_ostream<CharType> and StringType for character CharType) are unaffected. Verified with Apple clang 21 / libc++, with the two -Wdocumentation* "ignored" pragma lines in macro_scope.hpp temporarily removed and -std=c++11/c++20 plus the project's -Weverything flag set: calling to_cbor/to_msgpack/to_ubjson/to_bjdata/to_bson/to_bon8 on a std::vector<std::uint8_t> now produces no char_traits<unsigned char> (or any other) deprecation warning, while the char-based string- and ostream-adapter paths, and a to_cbor/from_cbor round trip, still compile and run correctly; also verified with GCC 16.2.0. Ran the full local test suite, including the binary-format unit tests (unit-cbor, unit-msgpack, unit-ubjson, unit-bjdata, unit-bson, unit-bon8, unit-binary_writer_sinks, unit-binary_formats, unit-custom-binary-type): 129/129 passing. #5725 item 2 (step a) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Remove the library-wide -Wdocumentation pragma; fix what it hid macro_scope.hpp / macro_unscope.hpp pushed and popped a Clang diagnostic region over the entire library that ignored -Wdocumentation and -Wdocumentation-unknown-command. Removed both pragmas and fixed every finding a full -Wdocumentation (which implies -Wdocumentation-unknown-command and -Wdocumentation-deprecated-sync) build reports, so the library now compiles clean under Clang's documentation checks without a blanket suppression. Overlaps #5267, which is still open and edits a nearby doc block (json.hpp's get()/get_impl() @return, already fixed in the item 2 step (b) commit of this branch); this commit does not touch that block again. Unknown Doxygen alias commands (Doxyfile removed in #3071, so these were never rendered by anything) rewritten as plain prose, keeping the same information: - @requirement REQ-JSON-01 / REQ-JSON-02 (iter_impl.hpp, json_reverse_iterator.hpp): now "This class satisfies the following concept requirements (REQ-JSON-0N):". - @liveexample{prose,example-id} (three sites in json.hpp): kept the prose, dropped the command wrapper and the trailing example-id (docs/mkdocs/docs/examples/*.cpp still exist and are used directly by the rendered docs, not through this in-header alias) and unescaped the "\," commas that were only needed for the old alias's comma-separated argument syntax. - @complexity X (json.hpp x4, json_pointer.hpp x2, serializer.hpp x1): now "Complexity: X". Backslash sequences Clang's comment lexer tried to parse as commands, escaped to render as literal backslashes: - lexer.hpp get_codepoint(): two `\u` occurrences. - binary_reader.hpp get_bson_cstr() / get_bson_cstr_bulk(): two `\x00` occurrences. - serializer.hpp: three `\uXXXX` occurrences (constructor @param, append_codepoint_to_string_buffer() @brief, and the ensure_ascii member comment). One finding remained after all of the above: Clang reports "declaration is marked with '@deprecated' command but does not have a deprecation attribute" on the deprecated sax_parse(span_input_adapter&&, ...) overload, even though JSON_HEDLEY_DEPRECATED_FOR does expand to __attribute__((deprecated(...))) for Clang. Several isolated reproductions of this exact declaration shape - doc comment, template<>, two stacked __attribute__ macros, an overload set sharing the name - did not reproduce the warning, so this looks like a Clang comment/declaration-association quirk specific to this overload inside the much larger basic_json class template, not an actual documentation defect. Rather than keep the pragma library-wide for one Clang false positive, added a tightly scoped -Wdocumentation-deprecated-sync push/pop around just that overload. Verified with Apple clang 21 and the project's actual -Weverything flag set (cmake/clang_flags.cmake) on the full header at -std=c++11 and -std=c++20: zero -Wdocumentation* diagnostics. Also compiled clean with GCC 16.2.0 (the pragmas are already __clang__-gated, so this only confirms no unrelated breakage). Ran make check-amalgamation and the full local test suite: 129/129 passing. #5725 item 2 (step c) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Take the JSON value by const reference in the array and tuple from_json paths Review feedback on #5737 (gregmarr): once the no-op std::forward calls are gone, the forwarding references have no purpose. from_json_fn passes the value as const BasicJsonType&, so these functions were only ever instantiated with a const lvalue anyway. The std::array, std::pair and std::tuple overloads of from_json and their helpers now take const BasicJsonType& and pass j on unchanged. Because the deduced BasicJsonType is now the plain type, tuple_type and the static_assert name const BasicJsonType& explicitly, so the reference checks are unchanged: get<std::tuple<const std::string&>>() still works, and get<std::tuple<std::string&>>() still fails the same static_assert. from_json_tuple_get_impl keeps its forwarding reference, since tuple_type calls it through std::declval. Behavior, the public API and the ABI do not change. unit-conversions, unit-constructor1, unit-udt, unit-udt_macro, unit-regression1/2/3, unit-deserialization, unit-noexcept, unit-items, unit-allocator, unit-custom-object-type, unit-ordered_json2 and unit-brace-init-copy-semantics pass at C++11, C++17 and C++20 with unchanged assertion counts. Ran make amalgamate. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
1001 lines
46 KiB
C++
1001 lines
46 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// cmake/test.cmake selects the C++ standard versions with which to build a
|
|
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
|
// When using macros that are only defined for particular versions of the standard
|
|
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
|
// version macro in a comment close by, like this:
|
|
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
|
|
|
#include "doctest_compatibility.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include <cctype>
|
|
#include <cstdint>
|
|
#include <map>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#define JSON_TESTS_PRIVATE
|
|
#include <nlohmann/json.hpp>
|
|
using nlohmann::json;
|
|
|
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
// this can be replaced with the doctest stl extension header in version 2.5
|
|
namespace doctest
|
|
{
|
|
template<> struct StringMaker<std::partial_ordering>
|
|
{
|
|
static String convert(const std::partial_ordering& order)
|
|
{
|
|
if (order == std::partial_ordering::less)
|
|
{
|
|
return "std::partial_ordering::less";
|
|
}
|
|
if (order == std::partial_ordering::equivalent)
|
|
{
|
|
return "std::partial_ordering::equivalent";
|
|
}
|
|
if (order == std::partial_ordering::greater)
|
|
{
|
|
return "std::partial_ordering::greater";
|
|
}
|
|
if (order == std::partial_ordering::unordered)
|
|
{
|
|
return "std::partial_ordering::unordered";
|
|
}
|
|
return "{?}";
|
|
}
|
|
};
|
|
} // namespace doctest
|
|
|
|
#endif
|
|
|
|
namespace
|
|
{
|
|
// helper function to check std::less<json::value_t>
|
|
// see https://en.cppreference.com/w/cpp/utility/functional/less
|
|
template <typename A, typename B, typename U = std::less<json::value_t>>
|
|
bool f(A a, B b, U u = U())
|
|
{
|
|
return u(a, b);
|
|
}
|
|
} // namespace
|
|
|
|
TEST_CASE("lexicographical comparison operators")
|
|
{
|
|
constexpr auto f_ = false;
|
|
constexpr auto _t = true;
|
|
constexpr auto nan = std::numeric_limits<json::number_float_t>::quiet_NaN();
|
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
constexpr auto lt = std::partial_ordering::less;
|
|
constexpr auto gt = std::partial_ordering::greater;
|
|
constexpr auto eq = std::partial_ordering::equivalent;
|
|
constexpr auto un = std::partial_ordering::unordered;
|
|
#endif
|
|
|
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
INFO("using 3-way comparison");
|
|
#endif
|
|
|
|
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
|
INFO("using legacy comparison");
|
|
#endif
|
|
|
|
//REQUIRE(std::numeric_limits<json::number_float_t>::has_quiet_NaN);
|
|
REQUIRE(std::isnan(nan));
|
|
|
|
SECTION("types")
|
|
{
|
|
std::vector<json::value_t> j_types =
|
|
{
|
|
json::value_t::null,
|
|
json::value_t::boolean,
|
|
json::value_t::number_integer,
|
|
json::value_t::number_unsigned,
|
|
json::value_t::number_float,
|
|
json::value_t::object,
|
|
json::value_t::array,
|
|
json::value_t::string,
|
|
json::value_t::binary,
|
|
json::value_t::discarded
|
|
};
|
|
|
|
std::vector<std::vector<bool>> expected_lt =
|
|
{
|
|
//0 1 2 3 4 5 6 7 8 9
|
|
{f_, _t, _t, _t, _t, _t, _t, _t, _t, f_}, // 0
|
|
{f_, f_, _t, _t, _t, _t, _t, _t, _t, f_}, // 1
|
|
{f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 2
|
|
{f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 3
|
|
{f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 4
|
|
{f_, f_, f_, f_, f_, f_, _t, _t, _t, f_}, // 5
|
|
{f_, f_, f_, f_, f_, f_, f_, _t, _t, f_}, // 6
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, _t, f_}, // 7
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9
|
|
};
|
|
|
|
SECTION("comparison: less")
|
|
{
|
|
REQUIRE(expected_lt.size() == j_types.size());
|
|
for (size_t i = 0; i < j_types.size(); ++i)
|
|
{
|
|
REQUIRE(expected_lt[i].size() == j_types.size());
|
|
for (size_t j = 0; j < j_types.size(); ++j)
|
|
{
|
|
CAPTURE(i)
|
|
CAPTURE(j)
|
|
// check precomputed values
|
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
CHECK((j_types[i] < j_types[j]) == expected_lt[i][j]);
|
|
#else
|
|
CHECK(operator<(j_types[i], j_types[j]) == expected_lt[i][j]);
|
|
#endif
|
|
CHECK(f(j_types[i], j_types[j]) == expected_lt[i][j]);
|
|
}
|
|
}
|
|
}
|
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
SECTION("comparison: 3-way")
|
|
{
|
|
std::vector<std::vector<std::partial_ordering>> expected =
|
|
{
|
|
//0 1 2 3 4 5 6 7 8 9
|
|
{eq, lt, lt, lt, lt, lt, lt, lt, lt, un}, // 0
|
|
{gt, eq, lt, lt, lt, lt, lt, lt, lt, un}, // 1
|
|
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 2
|
|
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 3
|
|
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 4
|
|
{gt, gt, gt, gt, gt, eq, lt, lt, lt, un}, // 5
|
|
{gt, gt, gt, gt, gt, gt, eq, lt, lt, un}, // 6
|
|
{gt, gt, gt, gt, gt, gt, gt, eq, lt, un}, // 7
|
|
{gt, gt, gt, gt, gt, gt, gt, gt, eq, un}, // 8
|
|
{un, un, un, un, un, un, un, un, un, un}, // 9
|
|
};
|
|
|
|
// check expected partial_ordering against expected boolean
|
|
REQUIRE(expected.size() == expected_lt.size());
|
|
for (size_t i = 0; i < expected.size(); ++i)
|
|
{
|
|
REQUIRE(expected[i].size() == expected_lt[i].size());
|
|
for (size_t j = 0; j < expected[i].size(); ++j)
|
|
{
|
|
CAPTURE(i)
|
|
CAPTURE(j)
|
|
CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
|
|
}
|
|
}
|
|
|
|
// check 3-way comparison against expected partial_ordering
|
|
REQUIRE(expected.size() == j_types.size());
|
|
for (size_t i = 0; i < j_types.size(); ++i)
|
|
{
|
|
REQUIRE(expected[i].size() == j_types.size());
|
|
for (size_t j = 0; j < j_types.size(); ++j)
|
|
{
|
|
CAPTURE(i)
|
|
CAPTURE(j)
|
|
CHECK((j_types[i] <=> j_types[j]) == expected[i][j]); // *NOPAD*
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
SECTION("values")
|
|
{
|
|
json j_values =
|
|
{
|
|
nullptr, nullptr, // 0 1
|
|
-17, 42, // 2 3
|
|
8u, 13u, // 4 5
|
|
3.14159, 23.42, // 6 7
|
|
nan, nan, // 8 9
|
|
"foo", "bar", // 10 11
|
|
true, false, // 12 13
|
|
{1, 2, 3}, {"one", "two", "three"}, // 14 15
|
|
{{"first", 1}, {"second", 2}}, {{"a", "A"}, {"b", {"B"}}}, // 16 17
|
|
json::binary({1, 2, 3}), json::binary({1, 2, 4}), // 18 19
|
|
json(json::value_t::discarded), json(json::value_t::discarded) // 20 21
|
|
};
|
|
|
|
std::vector<std::vector<bool>> expected_eq =
|
|
{
|
|
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
|
{_t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 0
|
|
{_t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 1
|
|
{f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 2
|
|
{f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 3
|
|
{f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 4
|
|
{f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 5
|
|
{f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 6
|
|
{f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 7
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 10
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 11
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 12
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_}, // 13
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_}, // 14
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_}, // 15
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_}, // 16
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_}, // 17
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_}, // 18
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_}, // 19
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21
|
|
};
|
|
|
|
std::vector<std::vector<bool>> expected_lt =
|
|
{
|
|
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
|
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_}, // 0
|
|
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_}, // 1
|
|
{f_, f_, f_, _t, _t, _t, _t, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 2
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 3
|
|
{f_, f_, f_, _t, f_, _t, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 4
|
|
{f_, f_, f_, _t, f_, f_, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 5
|
|
{f_, f_, f_, _t, _t, _t, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 6
|
|
{f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 7
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 8
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 9
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 10
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 11
|
|
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 12
|
|
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 13
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, _t, f_, f_, _t, _t, f_, f_}, // 14
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 15
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, f_, f_, _t, _t, f_, f_}, // 16
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, f_, _t, _t, f_, f_}, // 17
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_}, // 18
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 19
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21
|
|
};
|
|
|
|
SECTION("signed/unsigned mixed comparison above INT64_MAX")
|
|
{
|
|
const json above_int64_max = static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()) + 1ULL;
|
|
const json max_uint64 = (std::numeric_limits<std::uint64_t>::max)();
|
|
const json negative_one = -1;
|
|
const json one = 1;
|
|
const json max_int64 = (std::numeric_limits<std::int64_t>::max)();
|
|
|
|
CHECK_FALSE(above_int64_max == negative_one);
|
|
CHECK(above_int64_max != negative_one);
|
|
CHECK(negative_one < above_int64_max);
|
|
CHECK(negative_one <= above_int64_max);
|
|
CHECK_FALSE(negative_one > above_int64_max);
|
|
CHECK_FALSE(negative_one >= above_int64_max);
|
|
CHECK_FALSE(above_int64_max < negative_one);
|
|
CHECK_FALSE(above_int64_max <= negative_one);
|
|
CHECK(above_int64_max > negative_one);
|
|
CHECK(above_int64_max >= negative_one);
|
|
CHECK(negative_one != above_int64_max);
|
|
CHECK_FALSE(negative_one == above_int64_max);
|
|
|
|
CHECK_FALSE(max_uint64 == negative_one);
|
|
CHECK(max_uint64 != negative_one);
|
|
CHECK(negative_one < max_uint64);
|
|
CHECK(negative_one <= max_uint64);
|
|
CHECK_FALSE(negative_one > max_uint64);
|
|
CHECK_FALSE(negative_one >= max_uint64);
|
|
CHECK_FALSE(max_uint64 < negative_one);
|
|
CHECK_FALSE(max_uint64 <= negative_one);
|
|
CHECK(max_uint64 > negative_one);
|
|
CHECK(max_uint64 >= negative_one);
|
|
CHECK(negative_one != max_uint64);
|
|
CHECK_FALSE(negative_one == max_uint64);
|
|
|
|
CHECK_FALSE(one == above_int64_max);
|
|
CHECK(one != above_int64_max);
|
|
CHECK(one < above_int64_max);
|
|
CHECK(one <= above_int64_max);
|
|
CHECK_FALSE(one > above_int64_max);
|
|
CHECK_FALSE(one >= above_int64_max);
|
|
CHECK_FALSE(above_int64_max < one);
|
|
CHECK_FALSE(above_int64_max <= one);
|
|
CHECK(above_int64_max > one);
|
|
CHECK(above_int64_max >= one);
|
|
|
|
CHECK_FALSE(max_int64 == above_int64_max);
|
|
CHECK(max_int64 != above_int64_max);
|
|
CHECK(max_int64 < above_int64_max);
|
|
CHECK(max_int64 <= above_int64_max);
|
|
CHECK_FALSE(max_int64 > above_int64_max);
|
|
CHECK_FALSE(max_int64 >= above_int64_max);
|
|
CHECK_FALSE(above_int64_max < max_int64);
|
|
CHECK_FALSE(above_int64_max <= max_int64);
|
|
CHECK(above_int64_max > max_int64);
|
|
CHECK(above_int64_max >= max_int64);
|
|
|
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
CHECK((negative_one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
|
CHECK((above_int64_max <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
|
|
CHECK((negative_one <=> max_uint64) == std::partial_ordering::less); // *NOPAD*
|
|
CHECK((max_uint64 <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
|
|
CHECK((one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
|
CHECK((above_int64_max <=> one) == std::partial_ordering::greater); // *NOPAD*
|
|
CHECK((max_int64 <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
|
CHECK((above_int64_max <=> max_int64) == std::partial_ordering::greater); // *NOPAD*
|
|
#endif
|
|
}
|
|
|
|
SECTION("integer/float mixed comparison is exact")
|
|
{
|
|
// Widening the integer to a double loses precision past the
|
|
// mantissa, so 2^63-2 and 2^63-1 both used to compare equal to the
|
|
// double 2^63 while differing from each other. That makes equality
|
|
// intransitive and the ordering not a strict weak ordering.
|
|
const json below_two_63 = static_cast<std::int64_t>(9223372036854775806LL);
|
|
const json max_int64 = (std::numeric_limits<std::int64_t>::max)();
|
|
const json two_63 = 9223372036854775808.0;
|
|
|
|
CHECK_FALSE(below_two_63 == two_63);
|
|
CHECK_FALSE(max_int64 == two_63);
|
|
CHECK(below_two_63 != max_int64);
|
|
CHECK(below_two_63 < max_int64);
|
|
CHECK(below_two_63 < two_63);
|
|
CHECK(max_int64 < two_63);
|
|
CHECK(two_63 > max_int64);
|
|
CHECK_FALSE(two_63 < max_int64);
|
|
|
|
// the same past the unsigned range
|
|
const json max_uint64 = (std::numeric_limits<std::uint64_t>::max)();
|
|
const json two_64 = 18446744073709551616.0;
|
|
CHECK_FALSE(max_uint64 == two_64);
|
|
CHECK(max_uint64 < two_64);
|
|
CHECK(two_64 > max_uint64);
|
|
|
|
// values a double represents exactly still compare equal
|
|
CHECK(json(1) == json(1.0));
|
|
CHECK(json(1u) == json(1.0));
|
|
CHECK(json(-3) == json(-3.0));
|
|
CHECK(json(1) < json(1.5));
|
|
CHECK(json(1.5) < json(2));
|
|
CHECK(json(2) > json(1.5));
|
|
CHECK(json(-1) > json(-1.5));
|
|
CHECK(json(-1.5) < json(-1));
|
|
CHECK(json(-2) < json(-1.5));
|
|
|
|
// a float below the range of the integer type
|
|
CHECK(json(0) > json(-1e30));
|
|
CHECK(json(-1e30) < json(0));
|
|
CHECK(json(0u) > json(-0.5));
|
|
CHECK(json(-0.5) < json(0u));
|
|
|
|
// a NaN operand stays unordered against either integer kind
|
|
CHECK_FALSE(json(1) == json(nan));
|
|
CHECK_FALSE(json(1) < json(nan));
|
|
CHECK_FALSE(json(nan) < json(1));
|
|
CHECK_FALSE(json(1u) == json(nan));
|
|
|
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
CHECK((max_int64 <=> two_63) == std::partial_ordering::less); // *NOPAD*
|
|
CHECK((two_63 <=> max_int64) == std::partial_ordering::greater); // *NOPAD*
|
|
CHECK((below_two_63 <=> max_int64) == std::partial_ordering::less); // *NOPAD*
|
|
CHECK((max_uint64 <=> two_64) == std::partial_ordering::less); // *NOPAD*
|
|
CHECK((json(1) <=> json(1.0)) == std::partial_ordering::equivalent); // *NOPAD*
|
|
CHECK((json(1) <=> json(nan)) == std::partial_ordering::unordered); // *NOPAD*
|
|
#endif
|
|
}
|
|
|
|
SECTION("compares unordered")
|
|
{
|
|
std::vector<std::vector<bool>> expected =
|
|
{
|
|
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 0
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 1
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 2
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 3
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 4
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 5
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 6
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 7
|
|
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 8
|
|
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 9
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 10
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 11
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 12
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 13
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 14
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 15
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 16
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 17
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 18
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 19
|
|
{_t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t}, // 20
|
|
{_t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t}, // 21
|
|
};
|
|
|
|
// check if two values compare unordered as expected
|
|
REQUIRE(expected.size() == j_values.size());
|
|
for (size_t i = 0; i < j_values.size(); ++i)
|
|
{
|
|
REQUIRE(expected[i].size() == j_values.size());
|
|
for (size_t j = 0; j < j_values.size(); ++j)
|
|
{
|
|
CAPTURE(i)
|
|
CAPTURE(j)
|
|
CHECK(json::compares_unordered(j_values[i], j_values[j]) == expected[i][j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
|
SECTION("compares unordered (inverse)")
|
|
{
|
|
std::vector<std::vector<bool>> expected =
|
|
{
|
|
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 0
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 1
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 2
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 3
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 4
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 5
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 6
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 7
|
|
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8
|
|
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 10
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 11
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 12
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 13
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 14
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 15
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 16
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 17
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 18
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 19
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20
|
|
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21
|
|
};
|
|
|
|
// check that two values compare unordered as expected (with legacy-mode enabled)
|
|
REQUIRE(expected.size() == j_values.size());
|
|
for (size_t i = 0; i < j_values.size(); ++i)
|
|
{
|
|
REQUIRE(expected[i].size() == j_values.size());
|
|
for (size_t j = 0; j < j_values.size(); ++j)
|
|
{
|
|
CAPTURE(i)
|
|
CAPTURE(j)
|
|
CAPTURE(j_values[i])
|
|
CAPTURE(j_values[j])
|
|
CHECK(json::compares_unordered(j_values[i], j_values[j], true) == expected[i][j]);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
SECTION("comparison: equal")
|
|
{
|
|
// check that two values compare equal
|
|
REQUIRE(expected_eq.size() == j_values.size());
|
|
for (size_t i = 0; i < j_values.size(); ++i)
|
|
{
|
|
REQUIRE(expected_eq[i].size() == j_values.size());
|
|
for (size_t j = 0; j < j_values.size(); ++j)
|
|
{
|
|
CAPTURE(i)
|
|
CAPTURE(j)
|
|
CHECK((j_values[i] == j_values[j]) == expected_eq[i][j]);
|
|
}
|
|
}
|
|
|
|
// compare with null pointer
|
|
json j_null;
|
|
CHECK(j_null == nullptr);
|
|
CHECK(nullptr == j_null);
|
|
}
|
|
|
|
SECTION("comparison: not equal")
|
|
{
|
|
// check that two values compare unequal as expected
|
|
// operator!= now means exactly !(a==b) without special cases for NaN/discarded
|
|
for (size_t i = 0; i < j_values.size(); ++i)
|
|
{
|
|
for (size_t j = 0; j < j_values.size(); ++j)
|
|
{
|
|
CAPTURE(i)
|
|
CAPTURE(j)
|
|
|
|
CHECK((j_values[i] != j_values[j]) == !(j_values[i] == j_values[j]));
|
|
}
|
|
}
|
|
|
|
// compare with null pointer
|
|
const json j_null;
|
|
CHECK((j_null != nullptr) == !(j_null == nullptr));
|
|
CHECK((nullptr != j_null) == !(nullptr == j_null));
|
|
}
|
|
|
|
SECTION("comparison: less")
|
|
{
|
|
// check that two values compare less than as expected
|
|
REQUIRE(expected_lt.size() == j_values.size());
|
|
for (size_t i = 0; i < j_values.size(); ++i)
|
|
{
|
|
REQUIRE(expected_lt[i].size() == j_values.size());
|
|
for (size_t j = 0; j < j_values.size(); ++j)
|
|
{
|
|
CAPTURE(i)
|
|
CAPTURE(j)
|
|
CHECK((j_values[i] < j_values[j]) == expected_lt[i][j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("comparison: less than or equal equal")
|
|
{
|
|
// check that two values compare less than or equal as expected
|
|
for (size_t i = 0; i < j_values.size(); ++i)
|
|
{
|
|
for (size_t j = 0; j < j_values.size(); ++j)
|
|
{
|
|
CAPTURE(i)
|
|
CAPTURE(j)
|
|
if (json::compares_unordered(j_values[i], j_values[j], true))
|
|
{
|
|
// if two values compare unordered,
|
|
// check that the boolean comparison result is always false
|
|
CHECK_FALSE(j_values[i] <= j_values[j]);
|
|
}
|
|
else
|
|
{
|
|
// otherwise, check that they compare according to their definition
|
|
// as the inverse of less than with the operand order reversed
|
|
CHECK((j_values[i] <= j_values[j]) == !(j_values[j] < j_values[i]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("comparison: greater than")
|
|
{
|
|
// check that two values compare greater than as expected
|
|
for (size_t i = 0; i < j_values.size(); ++i)
|
|
{
|
|
for (size_t j = 0; j < j_values.size(); ++j)
|
|
{
|
|
CAPTURE(i)
|
|
CAPTURE(j)
|
|
if (json::compares_unordered(j_values[i], j_values[j]))
|
|
{
|
|
// if two values compare unordered,
|
|
// check that the boolean comparison result is always false
|
|
CHECK_FALSE(j_values[i] > j_values[j]);
|
|
}
|
|
else
|
|
{
|
|
// otherwise, check that they compare according to their definition
|
|
// as the inverse of less than or equal which is defined as
|
|
// the inverse of less than with the operand order reversed
|
|
CHECK((j_values[i] > j_values[j]) == !(j_values[i] <= j_values[j]));
|
|
CHECK((j_values[i] > j_values[j]) == !!(j_values[j] < j_values[i]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("comparison: greater than or equal")
|
|
{
|
|
// check that two values compare greater than or equal as expected
|
|
for (size_t i = 0; i < j_values.size(); ++i)
|
|
{
|
|
for (size_t j = 0; j < j_values.size(); ++j)
|
|
{
|
|
CAPTURE(i)
|
|
CAPTURE(j)
|
|
if (json::compares_unordered(j_values[i], j_values[j], true))
|
|
{
|
|
// if two values compare unordered,
|
|
// check that the boolean result is always false
|
|
CHECK_FALSE(j_values[i] >= j_values[j]);
|
|
}
|
|
else
|
|
{
|
|
// otherwise, check that they compare according to their definition
|
|
// as the inverse of less than
|
|
CHECK((j_values[i] >= j_values[j]) == !(j_values[i] < j_values[j]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
SECTION("comparison: 3-way")
|
|
{
|
|
std::vector<std::vector<std::partial_ordering>> expected =
|
|
{
|
|
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
|
{eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 0
|
|
{eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 1
|
|
{gt, gt, eq, lt, lt, lt, lt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 2
|
|
{gt, gt, gt, eq, gt, gt, gt, gt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 3
|
|
{gt, gt, gt, lt, eq, lt, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 4
|
|
{gt, gt, gt, lt, gt, eq, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 5
|
|
{gt, gt, gt, lt, lt, lt, eq, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 6
|
|
{gt, gt, gt, lt, gt, gt, gt, eq, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 7
|
|
{gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 8
|
|
{gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 9
|
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, gt, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 10
|
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, eq, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 11
|
|
{gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, gt, lt, lt, lt, lt, lt, lt, un, un}, // 12
|
|
{gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, lt, lt, lt, lt, lt, lt, un, un}, // 13
|
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, eq, lt, gt, gt, lt, lt, un, un}, // 14
|
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, gt, eq, gt, gt, lt, lt, un, un}, // 15
|
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, eq, gt, lt, lt, un, un}, // 16
|
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, lt, eq, lt, lt, un, un}, // 17
|
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, lt, un, un}, // 18
|
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, un, un}, // 19
|
|
{un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 20
|
|
{un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 21
|
|
};
|
|
|
|
// check expected partial_ordering against expected booleans
|
|
REQUIRE(expected.size() == expected_eq.size());
|
|
REQUIRE(expected.size() == expected_lt.size());
|
|
for (size_t i = 0; i < expected.size(); ++i)
|
|
{
|
|
REQUIRE(expected[i].size() == expected_eq[i].size());
|
|
REQUIRE(expected[i].size() == expected_lt[i].size());
|
|
for (size_t j = 0; j < expected[i].size(); ++j)
|
|
{
|
|
CAPTURE(i)
|
|
CAPTURE(j)
|
|
CHECK(std::is_eq(expected[i][j]) == expected_eq[i][j]);
|
|
CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
|
|
if (std::is_gt(expected[i][j]))
|
|
{
|
|
CHECK((!expected_eq[i][j] && !expected_lt[i][j]));
|
|
}
|
|
}
|
|
}
|
|
|
|
// check that two values compare according to their expected ordering
|
|
REQUIRE(expected.size() == j_values.size());
|
|
for (size_t i = 0; i < j_values.size(); ++i)
|
|
{
|
|
REQUIRE(expected[i].size() == j_values.size());
|
|
for (size_t j = 0; j < j_values.size(); ++j)
|
|
{
|
|
CAPTURE(i)
|
|
CAPTURE(j)
|
|
CHECK((j_values[i] <=> j_values[j]) == expected[i][j]); // *NOPAD*
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
|
SECTION("parser callback regression")
|
|
{
|
|
SECTION("filter specific element")
|
|
{
|
|
const auto* s_object = R"(
|
|
{
|
|
"foo": 2,
|
|
"bar": {
|
|
"baz": 1
|
|
}
|
|
}
|
|
)";
|
|
const auto* s_array = R"(
|
|
[1,2,[3,4,5],4,5]
|
|
)";
|
|
|
|
const json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t /*unused*/, const json & j) noexcept
|
|
{
|
|
// filter all number(2) elements
|
|
return j != json(2);
|
|
});
|
|
|
|
CHECK (j_object == json({{"bar", {{"baz", 1}}}}));
|
|
|
|
const json j_array = json::parse(s_array, [](int /*unused*/, json::parse_event_t /*unused*/, const json & j) noexcept
|
|
{
|
|
return j != json(2);
|
|
});
|
|
|
|
CHECK (j_array == json({1, {3, 4, 5}, 4, 5}));
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
|
|
TEST_CASE("regression #3868 - heterogeneous comparisons compile under C++20 (P2468R2)")
|
|
{
|
|
// Issue #3868: operator!= was preventing compiler from synthesizing reversed
|
|
// operator== candidates under C++20's P2468R2 rewritten candidate rules.
|
|
// Verify that heterogeneous comparisons now work.
|
|
|
|
SECTION("string vs json")
|
|
{
|
|
std::string s = "string";
|
|
json j = "string";
|
|
CHECK(s == j);
|
|
CHECK(j == s);
|
|
CHECK_FALSE(s != j);
|
|
CHECK_FALSE(j != s);
|
|
}
|
|
|
|
SECTION("other heterogeneous types")
|
|
{
|
|
int i = 42;
|
|
json j = 42;
|
|
CHECK(i == j);
|
|
CHECK(j == i);
|
|
CHECK_FALSE(i != j);
|
|
CHECK_FALSE(j != i);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
namespace
|
|
{
|
|
// orders keys ascending or descending, as chosen when a map is created
|
|
template<class Key>
|
|
class directed_less
|
|
{
|
|
public:
|
|
directed_less() = default;
|
|
|
|
explicit directed_less(const bool descending) noexcept
|
|
: m_descending(descending)
|
|
{}
|
|
|
|
bool operator()(const Key& lhs, const Key& rhs) const
|
|
{
|
|
return m_descending ? rhs < lhs : lhs < rhs;
|
|
}
|
|
|
|
private:
|
|
bool m_descending = false;
|
|
};
|
|
|
|
// An object type that, like std::unordered_map, enumerates its entries in no
|
|
// fixed order - ascending or descending by key, depending on how the map was
|
|
// created - and whose operator== does not depend on that order.
|
|
// std::unordered_map itself cannot be used here: the standard does not
|
|
// require it to accept an incomplete mapped type such as basic_json, and
|
|
// libstdc++ 6 to 9 as well as the EDG front ends of icpc and nvc++ reject
|
|
// basic_json<std::unordered_map>. std::map, the default object type, works
|
|
// with all supported compilers.
|
|
template<class Key, class Value, class /*Compare*/, class Allocator>
|
|
struct unordered_object_t : std::map<Key, Value, directed_less<Key>, Allocator>
|
|
{
|
|
using base_type = std::map<Key, Value, directed_less<Key>, Allocator>;
|
|
using base_type::base_type;
|
|
|
|
friend bool operator==(const unordered_object_t& lhs, const unordered_object_t& rhs)
|
|
{
|
|
return lhs.size() == rhs.size() && std::all_of(lhs.begin(), lhs.end(), [&rhs](const std::pair<const Key, Value>& entry)
|
|
{
|
|
const auto it = rhs.find(entry.first);
|
|
return it != rhs.end() && it->second == entry.second;
|
|
});
|
|
}
|
|
|
|
friend bool operator!=(const unordered_object_t& lhs, const unordered_object_t& rhs)
|
|
{
|
|
return !(lhs == rhs);
|
|
}
|
|
};
|
|
using unordered_json = nlohmann::basic_json<unordered_object_t>;
|
|
|
|
// the entries "0" to "9", enumerated in ascending or in descending order
|
|
unordered_json make_unordered_object(const bool descending)
|
|
{
|
|
unordered_json j = unordered_json::object_t(directed_less<std::string>(descending));
|
|
for (int i = 0; i < 10; ++i)
|
|
{
|
|
j[std::to_string(i)] = i;
|
|
}
|
|
return j;
|
|
}
|
|
|
|
template<typename Json>
|
|
Json nest(Json j, const std::size_t depth)
|
|
{
|
|
for (std::size_t i = 0; i < depth; ++i)
|
|
{
|
|
Json outer = Json::object();
|
|
outer["x"] = std::move(j);
|
|
j = std::move(outer);
|
|
}
|
|
return j;
|
|
}
|
|
|
|
// orders keys case-insensitively, so "key" and "KEY" compare equivalent
|
|
// (neither less than the other) although they are not equal
|
|
struct case_insensitive_less
|
|
{
|
|
bool operator()(const std::string& a, const std::string& b) const
|
|
{
|
|
return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(),
|
|
[](unsigned char x, unsigned char y)
|
|
{
|
|
return std::tolower(x) < std::tolower(y);
|
|
});
|
|
}
|
|
};
|
|
|
|
template<class Key, class Value, class /*Compare*/, class Allocator>
|
|
using case_insensitive_map = std::map<Key, Value, case_insensitive_less, Allocator>;
|
|
using ci_json = nlohmann::basic_json<case_insensitive_map>;
|
|
} // namespace
|
|
|
|
TEST_CASE("equality of objects whose entries have no fixed order")
|
|
{
|
|
// Values nested deeper than a bound are compared without the call stack,
|
|
// entry by entry. That must agree with the object type's own operator==,
|
|
// which for unordered_object_t (as for std::unordered_map) does not
|
|
// depend on the order of the entries, and for ordered_map does.
|
|
REQUIRE(make_unordered_object(true).begin().key() == "9");
|
|
REQUIRE(make_unordered_object(false).begin().key() == "0");
|
|
|
|
for (const std::size_t depth : std::vector<std::size_t> {0, 200})
|
|
{
|
|
CAPTURE(depth)
|
|
|
|
const unordered_json descending = nest(make_unordered_object(true), depth);
|
|
const unordered_json ascending = nest(make_unordered_object(false), depth);
|
|
CHECK(descending == ascending);
|
|
CHECK_FALSE(descending != ascending);
|
|
|
|
// a copy is equal to its original
|
|
const unordered_json copy = descending; // NOLINT(performance-unnecessary-copy-initialization)
|
|
CHECK(copy == descending);
|
|
|
|
// a different value, a different key, or another entry still count
|
|
unordered_json other_value = make_unordered_object(true);
|
|
other_value["5"] = 42;
|
|
CHECK_FALSE(nest(other_value, depth) == ascending);
|
|
|
|
unordered_json other_key = make_unordered_object(true);
|
|
other_key.erase("5");
|
|
other_key["50"] = 5;
|
|
CHECK_FALSE(nest(other_key, depth) == ascending);
|
|
|
|
unordered_json more_entries = make_unordered_object(true);
|
|
more_entries["10"] = 10;
|
|
CHECK_FALSE(nest(more_entries, depth) == ascending);
|
|
CHECK_FALSE(ascending == nest(more_entries, depth));
|
|
|
|
// ordered_json compares its entries in sequence
|
|
const nlohmann::ordered_json ab = nest(nlohmann::ordered_json({{"a", 1}, {"b", 2}}), depth);
|
|
const nlohmann::ordered_json ba = nest(nlohmann::ordered_json({{"b", 2}, {"a", 1}}), depth);
|
|
CHECK_FALSE(ab == ba);
|
|
CHECK(ab != ba);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("equality of an object whose comparator treats different keys as equivalent")
|
|
{
|
|
// https://github.com/nlohmann/json/issues/5655: past the nesting bound,
|
|
// the entries are compared without the call stack, and a key that finds
|
|
// no counterpart at the same position is looked up with find(), which
|
|
// uses the object's own comparator. A case-insensitive comparator then
|
|
// finds "KEY" for "key" and must not accept that pair as a match - the
|
|
// object type's own operator==, like std::map's, compares keys with ==.
|
|
ci_json a = ci_json::object();
|
|
a["key"] = 1;
|
|
ci_json b = ci_json::object();
|
|
b["KEY"] = 1;
|
|
|
|
// sanity check: the object type's own comparison already disagrees
|
|
CHECK_FALSE(a.get_ref<const ci_json::object_t&>() == b.get_ref<const ci_json::object_t&>());
|
|
|
|
for (const std::size_t depth : std::vector<std::size_t> {0, 127, 128, 200})
|
|
{
|
|
CAPTURE(depth)
|
|
|
|
const ci_json x = nest(a, depth);
|
|
const ci_json y = nest(b, depth);
|
|
CHECK_FALSE(x == y);
|
|
CHECK(x != y);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("containers are compared element by element")
|
|
{
|
|
// Containers nested deeper than a bound are compared without the call
|
|
// stack, by code of their own; every relation is checked both at the top
|
|
// level and below that bound.
|
|
const auto deep = [](const json & j, const std::size_t depth)
|
|
{
|
|
json result = j;
|
|
for (std::size_t i = 0; i < depth; ++i)
|
|
{
|
|
result = json::array({std::move(result)});
|
|
}
|
|
return result;
|
|
};
|
|
|
|
for (const std::size_t depth : std::vector<std::size_t> {0, 200})
|
|
{
|
|
CAPTURE(depth)
|
|
|
|
// objects with different keys
|
|
{
|
|
const json a = deep({{"a", 1}}, depth);
|
|
const json b = deep({{"b", 1}}, depth);
|
|
CHECK_FALSE(a == b);
|
|
CHECK(a != b);
|
|
CHECK(a < b);
|
|
CHECK(b > a);
|
|
CHECK_FALSE(b < a);
|
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
CHECK((a <=> b) == std::partial_ordering::less); // *NOPAD*
|
|
CHECK((b <=> a) == std::partial_ordering::greater); // *NOPAD*
|
|
CHECK((a <=> a) == std::partial_ordering::equivalent); // *NOPAD*
|
|
#endif
|
|
}
|
|
|
|
// a container that is a prefix of the other one
|
|
{
|
|
// the one that runs out of elements first is the smaller one
|
|
const json shorter = deep({1}, depth);
|
|
const json longer = deep({1, 2}, depth);
|
|
CHECK(shorter < longer);
|
|
CHECK(longer > shorter);
|
|
CHECK_FALSE(longer < shorter);
|
|
CHECK_FALSE(shorter == longer);
|
|
|
|
const json smaller_object = deep({{"a", 1}}, depth);
|
|
const json larger_object = deep({{"a", 1}, {"b", 2}}, depth);
|
|
CHECK(smaller_object < larger_object);
|
|
CHECK(larger_object > smaller_object);
|
|
CHECK_FALSE(smaller_object == larger_object);
|
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
CHECK((shorter <=> longer) == std::partial_ordering::less); // *NOPAD*
|
|
CHECK((longer <=> shorter) == std::partial_ordering::greater); // *NOPAD*
|
|
#endif
|
|
}
|
|
|
|
// elements that cannot be ordered
|
|
{
|
|
const double nan = std::numeric_limits<double>::quiet_NaN();
|
|
const json lhs = deep({nan, 1}, depth);
|
|
const json rhs = deep({nan, 2}, depth);
|
|
|
|
CHECK_FALSE(lhs == lhs);
|
|
CHECK_FALSE(rhs < lhs);
|
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
// operator<=> stops there, as std::lexicographical_compare_three_way
|
|
// does, and operator< is derived from it
|
|
CHECK((lhs <=> rhs) == std::partial_ordering::unordered); // *NOPAD*
|
|
CHECK_FALSE(lhs < rhs);
|
|
#else
|
|
// operator< skips a pair of elements that cannot be ordered, as
|
|
// std::lexicographical_compare does, and the next pair decides
|
|
CHECK(lhs < rhs);
|
|
#endif
|
|
}
|
|
}
|
|
}
|