mirror of
https://github.com/nlohmann/json.git
synced 2026-11-14 03:18:20 +00:00
Copying, comparing, serializing, hashing, merging, diffing, converting and destroying a value, and the CBOR, MessagePack, UBJSON, BJData and BON8 writers recurse a fixed number of nesting levels on the call stack and finish deeper levels with an explicit stack (#5387). The number was hard-coded to 128. That is too much for small stacks: a 128-level dump() needs about 288 KiB at -O0 with Clang (2.3 KiB per level; 256 KiB with GCC) and crashed in a thread with a 256 KiB stack, and musl threads have 128 KiB by default. Others may want the opposite. The macro JSON_RECURSION_DEPTH_LIMIT, defined before including the library, sets the number; the default stays 128. The value must be an integer constant from 0 to 254 (not a floating-point number or bool), otherwise a static_assert names the macro. 254 is the largest value because the copy constructor and the comparison operators cannot pass a depth down and count it in a byte per thread (nesting_depth()). The guard that counts now counts only the levels it enters, so the count never exceeds the limit. It used to count the level that finds the limit reached as well, which made the count 255 at limit 254: a copy made by a custom base class while a copy was going on then wrapped the byte to 0 and recursed another 254 levels (twice the stack). 0 is allowed: every walk then uses its explicit stack from the first level on and needs a constant amount of stack (16 KiB or less at -O0 with Clang and GCC, for a value nested 20,000 levels deep). All uses of the limit compare a depth with it; none computes limit - 1 or limit + 1, and no walk has a constant 128 of its own. The macro changes only stack use and speed, never a result. Like JSON_NO_THREAD_LOCAL it is not part of the ABI tag and must be the same in all translation units (ODR); the page of the macro says what happens otherwise. Supporting a limit of 0 needed one fix: update() without merge_objects reached the explicit-stack merge at depth 0, which always merges objects. update_members now takes that path only with merge_objects, which is the only case in which it recurses at all. Measurements (macOS arm64, Apple clang 21 and g++ 16, documented in the macro page with a table per operation and optimization level, and a program to reproduce them): the stack needed at the default of 128 levels is up to 288 KiB (dump at -O0, Clang), 224 KiB (copy), and 48 KiB or less at -O2; at 254 levels up to 576 KiB at -O0 and 112 KiB at -O2. Speed at -O2 relative to the default: ordinary documents (nested 2 to 10 levels) are 1.1 to 2.4 times as slow with a limit of 0 or 1 and unchanged (within about 0.1) with 16 or 254; values nested 1,000 or 100,000 levels deep show no difference for any limit. The default build does the same work as before: the instructions retired for copy and conversion (the walks whose guard changed; net of loading, canada, citm, twitter, jeopardy and deep values), ==, dump and hash differ from the base by at most 0.02 % for copy and 0.71 % in either direction for the others (noise), and the cycles for copy by at most 0.65 %. Tests: the existing tests that depended on 128 (comparison, allocator, custom base class, diagnostics, binary writers, patch, merge_diff, conversions, custom array type) derive their depths from the limit, and two assumed a limit of at least 1 (limit - 1 wrapped around). tests/src/unit-recursion_depth_limit.cpp checks that the macro is honoured (the array type of a value counts the levels that a copy and a comparison descend) and that all walks return the same results for values nested 1 to 300 levels deep as arrays, objects and both, against expected strings and bytes built independently of the library; CMake builds it with limits 0, 1 and 254 besides the default, and the CI target ci_test_recursion_depth_limit_0 runs the whole suite with a limit of 0. A test with a base class that copies the value from its copy constructor covers the wrap-around at 254. The new test does not compile against the previous header with -DJSON_RECURSION_DEPTH_LIMIT=1. Docs: new macro page (range, stack and speed tables, small-stack guidance, ODR), index, nav, macro overview, "Nesting depth" of the untrusted-input page (which claimed that no walk could exhaust the stack however deep a value is nested; the default assumes a stack of a few hundred KiB), the assurance case, and the page of JSON_NO_THREAD_LOCAL. No breaking changes: the default is unchanged, and the macro is new. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
1317 lines
57 KiB
C++
1317 lines
57 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 <limits>
|
|
#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
|
|
{
|
|
// nesting depths on either side of detail::recursion_depth_limit(): none, the
|
|
// last one within the limit, the first one past it, and well past it (0, 127,
|
|
// 128, and 200 by default)
|
|
std::vector<std::size_t> depths_around_recursion_limit()
|
|
{
|
|
const std::size_t limit = nlohmann::detail::recursion_depth_limit();
|
|
return {0, limit > 0 ? limit - 1 : 0, limit, limit + 72};
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
|
TEST_CASE("regression #5665 - scalar <= discarded and scalar >= discarded in C++20 legacy mode")
|
|
{
|
|
// Issue #5665: with a scalar on the left-hand side, <= and >= only had the
|
|
// candidate rewritten from operator<=>, which does not emulate the legacy
|
|
// discarded-value behavior. Check that scalar-on-the-left now matches the
|
|
// other three operand orders.
|
|
const json discarded(json::value_t::discarded);
|
|
const json one = 1;
|
|
|
|
CHECK(discarded <= 1);
|
|
CHECK(discarded >= 1);
|
|
CHECK(one <= discarded);
|
|
CHECK(one >= discarded);
|
|
CHECK(1 <= discarded);
|
|
CHECK(1 >= discarded);
|
|
CHECK(1.5 <= discarded);
|
|
CHECK(1.5 >= discarded);
|
|
}
|
|
#endif
|
|
|
|
#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::json::with_object_t<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;
|
|
}
|
|
|
|
// a std::map comparator with state: case-insensitive, unless constructed
|
|
// case-sensitive. Used to check that copying an object copies the original's
|
|
// comparator rather than default-constructing a new one (see #5649).
|
|
struct key_case_less
|
|
{
|
|
key_case_less() = default;
|
|
explicit key_case_less(const bool cs) noexcept : case_sensitive(cs) {}
|
|
|
|
bool operator()(const std::string& a, const std::string& b) const
|
|
{
|
|
if (case_sensitive)
|
|
{
|
|
return a < b;
|
|
}
|
|
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);
|
|
});
|
|
}
|
|
|
|
bool case_sensitive = false;
|
|
};
|
|
|
|
template<class Key, class Value, class /*Compare*/, class Allocator>
|
|
using key_case_map = std::map<Key, Value, key_case_less, Allocator>;
|
|
using key_case_json = nlohmann::json::with_object_t<key_case_map>;
|
|
|
|
// the innermost value of a chain of single-element arrays
|
|
template<typename Json>
|
|
const Json& innermost(const Json& j)
|
|
{
|
|
const Json* p = &j;
|
|
while (p->is_array())
|
|
{
|
|
p = &(*p)[0];
|
|
}
|
|
return *p;
|
|
}
|
|
|
|
// 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::json::with_object_t<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("copying an object preserves its comparator's state")
|
|
{
|
|
// Past the iterative deep copy's nesting bound, an object copy used to be
|
|
// built with a default-constructed comparator instead of a copy of the
|
|
// original's. For an object type whose comparator carries state - here, a
|
|
// std::map that compares keys case-sensitively only when created that way
|
|
// - this reordered the copy's keys and could even drop entries that the
|
|
// original's comparator kept distinct (see #5649).
|
|
key_case_json object = key_case_json::object_t(key_case_less(true)); // case-sensitive
|
|
object["b"] = 1;
|
|
object["B"] = 2;
|
|
object["a"] = 3;
|
|
REQUIRE(object.dump() == R"({"B":2,"a":3,"b":1})");
|
|
|
|
for (const std::size_t depth : depths_around_recursion_limit())
|
|
{
|
|
CAPTURE(depth)
|
|
|
|
key_case_json original = object;
|
|
for (std::size_t i = 0; i < depth; ++i)
|
|
{
|
|
original = key_case_json::array({std::move(original)});
|
|
}
|
|
|
|
{
|
|
const key_case_json copy = original; // NOLINT(performance-unnecessary-copy-initialization)
|
|
CHECK(innermost(copy).size() == 3);
|
|
CHECK(innermost(copy).dump() == R"({"B":2,"a":3,"b":1})");
|
|
CHECK(copy == original);
|
|
}
|
|
|
|
{
|
|
key_case_json copy = key_case_json::array();
|
|
copy = original;
|
|
CHECK(innermost(copy).size() == 3);
|
|
CHECK(innermost(copy).dump() == R"({"B":2,"a":3,"b":1})");
|
|
CHECK(copy == original);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 : depths_around_recursion_limit())
|
|
{
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
|
TEST_CASE("operator<=> of binary values with a different subtype does not depend on nesting depth")
|
|
{
|
|
// #5654: std::vector<std::uint8_t>::operator<=>, which the binary type's
|
|
// own operator<=> uses, ignores the subtype that operator== checks. So a
|
|
// pair of binary values with the same bytes but a different subtype is
|
|
// unequal, yet <=>-equivalent - the same inconsistency between == and <=>
|
|
// that a NaN has. Within the nesting bound, an array compares itself
|
|
// with std::vector's own operator<=>, which treats an equivalent pair as
|
|
// undecided and lets the next element decide, same as
|
|
// std::lexicographical_compare_three_way does. Past the bound,
|
|
// compare_iteratively<true>() takes over and must classify the pair the
|
|
// same way, or the result of operator<=> - and of <, which C++20 derives
|
|
// from it - depends on how deeply the values are nested.
|
|
const json a = json::array({json::binary({1}, 1), 1});
|
|
const json b = json::array({json::binary({1}, 2), 2});
|
|
|
|
// the root inconsistency: unequal, yet <=>-equivalent
|
|
CHECK_FALSE(a[0] == b[0]);
|
|
CHECK((a[0] <=> b[0]) == std::partial_ordering::equivalent); // *NOPAD*
|
|
|
|
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;
|
|
};
|
|
|
|
// one level less than the recursion depth limit (127 by default) stays within it;
|
|
// the limit itself and more (128 and 200) do not, and must still agree with the
|
|
// levels that do
|
|
for (const std::size_t depth : depths_around_recursion_limit())
|
|
{
|
|
CAPTURE(depth)
|
|
const json x = deep(a, depth);
|
|
const json y = deep(b, depth);
|
|
CHECK((x <=> y) == std::partial_ordering::less); // *NOPAD*
|
|
CHECK((y <=> x) == std::partial_ordering::greater); // *NOPAD*
|
|
CHECK(x < y);
|
|
CHECK(y > x);
|
|
CHECK_FALSE(y < x);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
TEST_CASE("comparison of scalars (inline fast path)")
|
|
{
|
|
const json::number_integer_t neg = -1;
|
|
const json::number_integer_t pos = 5;
|
|
const json::number_unsigned_t big = (std::numeric_limits<json::number_unsigned_t>::max)();
|
|
|
|
SECTION("same type")
|
|
{
|
|
CHECK(json(pos) == json(pos));
|
|
CHECK(json(pos) != json(neg));
|
|
CHECK(json(neg) < json(pos));
|
|
CHECK_FALSE(json(pos) < json(neg));
|
|
CHECK_FALSE(json(pos) < json(pos));
|
|
CHECK(json(pos) <= json(pos));
|
|
|
|
CHECK(json(1u) == json(1u));
|
|
CHECK(json(1u) < json(2u));
|
|
CHECK_FALSE(json(2u) < json(1u));
|
|
|
|
CHECK(json(1.5) == json(1.5));
|
|
CHECK(json(1.5) < json(2.5));
|
|
CHECK_FALSE(json(2.5) < json(1.5));
|
|
CHECK(json(-0.0) == json(0.0));
|
|
}
|
|
|
|
SECTION("mixed number types")
|
|
{
|
|
// negative signed vs unsigned must not wrap around
|
|
CHECK(json(neg) < json(1u));
|
|
CHECK(json(1u) > json(neg));
|
|
CHECK(json(neg) != json(big));
|
|
CHECK(json(neg) < json(big));
|
|
CHECK_FALSE(json(big) < json(neg));
|
|
|
|
CHECK(json(pos) == json(5u));
|
|
CHECK(json(5u) == json(pos));
|
|
CHECK(json(pos) < json(6u));
|
|
CHECK(json(pos) == json(5.0));
|
|
CHECK(json(5.0) == json(pos));
|
|
CHECK(json(5u) == json(5.0));
|
|
CHECK(json(neg) < json(0.5));
|
|
CHECK(json(0.5) < json(1u));
|
|
}
|
|
|
|
SECTION("boolean and null")
|
|
{
|
|
CHECK(json(true) == json(true));
|
|
CHECK(json(true) != json(false));
|
|
CHECK(json(false) < json(true));
|
|
CHECK_FALSE(json(true) < json(false));
|
|
CHECK_FALSE(json(true) < json(true));
|
|
|
|
CHECK(json(nullptr) == json(nullptr));
|
|
CHECK_FALSE(json(nullptr) < json(nullptr));
|
|
CHECK(json(nullptr) <= json(nullptr));
|
|
CHECK(json(nullptr) < json(false));
|
|
CHECK(json(nullptr) != json(false));
|
|
CHECK(json(nullptr) != json(0));
|
|
}
|
|
|
|
SECTION("string vs number")
|
|
{
|
|
CHECK(json("5") != json(5));
|
|
CHECK(json(5) != json("5"));
|
|
CHECK(json(5) < json("5")); // number < string by type order
|
|
CHECK_FALSE(json("5") < json(5));
|
|
CHECK(json(1.5) < json(""));
|
|
CHECK(json("a") == json("a"));
|
|
CHECK(json("a") < json("b"));
|
|
}
|
|
|
|
SECTION("NaN")
|
|
{
|
|
const json nan = std::numeric_limits<json::number_float_t>::quiet_NaN();
|
|
const json one = 1.0;
|
|
|
|
CHECK_FALSE(nan == nan);
|
|
CHECK(nan != nan);
|
|
CHECK_FALSE(nan < nan);
|
|
CHECK_FALSE(nan <= nan);
|
|
CHECK_FALSE(nan >= nan);
|
|
CHECK_FALSE(nan > nan);
|
|
|
|
CHECK_FALSE(nan == one);
|
|
CHECK_FALSE(one == nan);
|
|
CHECK_FALSE(nan < one);
|
|
CHECK_FALSE(one < nan);
|
|
CHECK_FALSE(nan <= one);
|
|
CHECK_FALSE(one <= nan);
|
|
CHECK_FALSE(nan >= one);
|
|
CHECK_FALSE(one >= nan);
|
|
|
|
// NaN against other number types
|
|
CHECK_FALSE(nan == json(1));
|
|
CHECK_FALSE(json(1) == nan);
|
|
CHECK_FALSE(nan < json(1u));
|
|
CHECK_FALSE(json(1u) < nan);
|
|
|
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
CHECK((nan <=> nan) == std::partial_ordering::unordered); // *NOPAD*
|
|
CHECK((nan <=> one) == std::partial_ordering::unordered); // *NOPAD*
|
|
CHECK((one <=> nan) == std::partial_ordering::unordered); // *NOPAD*
|
|
CHECK((one <=> json(2.0)) == std::partial_ordering::less); // *NOPAD*
|
|
CHECK((json(2.0) <=> one) == std::partial_ordering::greater); // *NOPAD*
|
|
CHECK((one <=> one) == std::partial_ordering::equivalent); // *NOPAD*
|
|
CHECK((json(neg) <=> json(1u)) == std::partial_ordering::less); // *NOPAD*
|
|
CHECK((json(nullptr) <=> json(nullptr)) == std::partial_ordering::equivalent); // *NOPAD*
|
|
CHECK((json(true) <=> json(false)) == std::partial_ordering::greater); // *NOPAD*
|
|
#endif
|
|
}
|
|
|
|
SECTION("discarded")
|
|
{
|
|
const json d(json::value_t::discarded);
|
|
|
|
CHECK_FALSE(d == d);
|
|
CHECK_FALSE(d < d);
|
|
CHECK_FALSE(d == json(1));
|
|
CHECK_FALSE(json(1) == d);
|
|
CHECK_FALSE(d < json(1));
|
|
CHECK_FALSE(json(1) < d);
|
|
CHECK_FALSE(d == json(nullptr));
|
|
CHECK_FALSE(json(nullptr) == d);
|
|
|
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
CHECK((d <=> d) == std::partial_ordering::unordered); // *NOPAD*
|
|
CHECK((d <=> json(1)) == std::partial_ordering::unordered); // *NOPAD*
|
|
CHECK((json(1) <=> d) == std::partial_ordering::unordered); // *NOPAD*
|
|
#endif
|
|
}
|
|
|
|
SECTION("scalar overloads")
|
|
{
|
|
CHECK(json(5) == 5);
|
|
CHECK(5 == json(5));
|
|
CHECK(json(5) < 6);
|
|
CHECK(4 < json(5));
|
|
CHECK(json(1.5) == 1.5);
|
|
CHECK(json(true) == true);
|
|
CHECK(json(5) != 6);
|
|
CHECK(json(1u) == 1.0);
|
|
}
|
|
|
|
SECTION("structured values still compare")
|
|
{
|
|
CHECK(json({1, 2}) == json({1, 2}));
|
|
CHECK(json({1, 2}) < json({1, 3}));
|
|
CHECK(json({{"a", 1}}) == json({{"a", 1}}));
|
|
CHECK(json::binary({1, 2}) == json::binary({1, 2}));
|
|
CHECK(json(1) < json::array());
|
|
CHECK(json::object() < json::array());
|
|
}
|
|
}
|