mirror of
https://github.com/nlohmann/json.git
synced 2026-10-09 07:57:13 +00:00
* Use the with_*_t aliases in tests, examples, and docs Replace spelled-out basic_json<...> instantiations that only change one or two template parameters with nlohmann::json::with_*_t (or ordered_json::with_*_t when the object type is ordered_map). Types that change all three number types chain with_integers_t and with_float_t. The raw basic_json<...> spelling stays where the template parameter list itself is the subject: the alias tests in unit-udt.cpp, explicit instantiations, and the ordered_json/compile-time docs. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix unit-large_json for clang and JSON_DIAGNOSTICS Two test problems from #5781 broke CI on develop: CAPTURE(depth); trips clang's -Wextra-semi-stmt, and the type_error.321 messages did not account for the diagnostics path prefix. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Use static_cast in unit-hash for clang-tidy #5772 added functional casts that clang-tidy reports as C-style casts (google-readability-casting). Also append a char instead of a one-character string in unit-large_json. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Declare the expected message prefix const in unit-large_json Without JSON_DIAGNOSTICS the prefix was never modified, which clang-tidy reports (misc-const-correctness). Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
258 lines
9.4 KiB
C++
258 lines
9.4 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
|
|
|
|
#include "doctest_compatibility.h"
|
|
|
|
#include <nlohmann/json.hpp>
|
|
using json = nlohmann::json;
|
|
using ordered_json = nlohmann::ordered_json;
|
|
|
|
#include <limits>
|
|
#include <set>
|
|
#include <string>
|
|
#include <unordered_set>
|
|
|
|
namespace
|
|
{
|
|
// how detail::hash defines the hash of an array or object: the seeds of the
|
|
// elements, combined in order. Recursive, so only usable on values nested a
|
|
// few hundred levels deep - which is exactly what is needed to check that the
|
|
// iterative path taken below detail::recursion_depth_limit() computes the same.
|
|
template<typename BasicJsonType>
|
|
std::size_t reference_hash(const BasicJsonType& j)
|
|
{
|
|
using nlohmann::detail::combine;
|
|
using string_t = typename BasicJsonType::string_t;
|
|
|
|
if (!j.is_structured())
|
|
{
|
|
return std::hash<BasicJsonType> {}(j);
|
|
}
|
|
|
|
auto seed = combine(static_cast<std::size_t>(j.type()), j.size());
|
|
for (const auto& element : j.items())
|
|
{
|
|
if (j.is_object())
|
|
{
|
|
seed = combine(seed, std::hash<string_t> {}(element.key()));
|
|
}
|
|
seed = combine(seed, reference_hash(element.value()));
|
|
}
|
|
return seed;
|
|
}
|
|
|
|
// a value nested `depth` levels deep, with siblings on every level
|
|
template<typename BasicJsonType>
|
|
BasicJsonType nested(const std::size_t depth, const bool objects)
|
|
{
|
|
BasicJsonType value = "leaf";
|
|
for (std::size_t i = 0; i < depth; ++i)
|
|
{
|
|
if (objects)
|
|
{
|
|
value = BasicJsonType{{"before", i}, {"nested", std::move(value)}, {"after", {i, "x"}}};
|
|
}
|
|
else
|
|
{
|
|
value = BasicJsonType::array({i, std::move(value), BasicJsonType::object({{"k", i}})});
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
std::string nested_text(const std::size_t depth, const bool objects)
|
|
{
|
|
std::string text;
|
|
if (objects)
|
|
{
|
|
text.reserve((6 * depth) + 1);
|
|
for (std::size_t i = 0; i < depth; ++i)
|
|
{
|
|
text += "{\"a\":";
|
|
}
|
|
text += "1";
|
|
text.append(depth, '}');
|
|
}
|
|
else
|
|
{
|
|
text.assign(depth, '[');
|
|
text += "1";
|
|
text.append(depth, ']');
|
|
}
|
|
return text;
|
|
}
|
|
} // namespace
|
|
|
|
TEST_CASE("hash<nlohmann::json>")
|
|
{
|
|
// Collect hashes for different JSON values and make sure that they are distinct
|
|
// We cannot compare against fixed values, because the implementation of
|
|
// std::hash may differ between compilers.
|
|
//
|
|
// numbers that compare equal under operator== (0 == 0U == 0.0) must hash
|
|
// equally, so they are only inserted once below and checked separately.
|
|
|
|
std::set<std::size_t> hashes;
|
|
|
|
// null
|
|
hashes.insert(std::hash<json> {}(json(nullptr)));
|
|
|
|
// boolean
|
|
hashes.insert(std::hash<json> {}(json(true)));
|
|
hashes.insert(std::hash<json> {}(json(false)));
|
|
|
|
// string
|
|
hashes.insert(std::hash<json> {}(json("")));
|
|
hashes.insert(std::hash<json> {}(json("foo")));
|
|
|
|
// number
|
|
hashes.insert(std::hash<json> {}(json(0)));
|
|
hashes.insert(std::hash<json> {}(json(-1)));
|
|
hashes.insert(std::hash<json> {}(json(42.23)));
|
|
|
|
// array
|
|
hashes.insert(std::hash<json> {}(json::array()));
|
|
hashes.insert(std::hash<json> {}(json::array({1, 2, 3})));
|
|
|
|
// object
|
|
hashes.insert(std::hash<json> {}(json::object()));
|
|
hashes.insert(std::hash<json> {}(json::object({{"foo", "bar"}})));
|
|
|
|
// binary
|
|
hashes.insert(std::hash<json> {}(json::binary({})));
|
|
hashes.insert(std::hash<json> {}(json::binary({}, 0)));
|
|
hashes.insert(std::hash<json> {}(json::binary({}, 42)));
|
|
hashes.insert(std::hash<json> {}(json::binary({1, 2, 3})));
|
|
hashes.insert(std::hash<json> {}(json::binary({1, 2, 3}, 0)));
|
|
hashes.insert(std::hash<json> {}(json::binary({1, 2, 3}, 42)));
|
|
|
|
// discarded
|
|
hashes.insert(std::hash<json> {}(json(json::value_t::discarded)));
|
|
|
|
CHECK(hashes.size() == 19);
|
|
|
|
// numbers that compare equal under operator== must hash equally,
|
|
// regardless of which of number_integer, number_unsigned, or
|
|
// number_float actually holds the value
|
|
CHECK(json(0) == json(static_cast<unsigned>(0)));
|
|
CHECK(json(0) == json(0.0));
|
|
CHECK(std::hash<json> {}(json(0)) == std::hash<json> {}(json(static_cast<unsigned>(0))));
|
|
CHECK(std::hash<json> {}(json(0)) == std::hash<json> {}(json(0.0)));
|
|
CHECK(std::hash<json> {}(json(-1)) == std::hash<json> {}(json(-1.0)));
|
|
|
|
// a std::unordered_set relies on this same consistency between == and hash
|
|
const std::unordered_set<json> numbers {json(0), json(static_cast<unsigned>(0)), json(0.0)};
|
|
CHECK(numbers.size() == 1);
|
|
|
|
// -0.0 compares equal to 0 and 0.0
|
|
CHECK(json(-0.0) == json(0));
|
|
CHECK(std::hash<json> {}(json(-0.0)) == std::hash<json> {}(json(0)));
|
|
CHECK(std::hash<json> {}(json(-0.0)) == std::hash<json> {}(json(0.0)));
|
|
|
|
// the ends of the integer ranges, which equal floats exactly
|
|
const auto int_min = (std::numeric_limits<json::number_integer_t>::min)();
|
|
const auto int_max = (std::numeric_limits<json::number_integer_t>::max)();
|
|
const auto two_63 = static_cast<json::number_unsigned_t>(1) << 63U;
|
|
CHECK(json(int_min) == json(-9223372036854775808.0));
|
|
CHECK(std::hash<json> {}(json(int_min)) == std::hash<json> {}(json(-9223372036854775808.0)));
|
|
CHECK(json(two_63) == json(9223372036854775808.0));
|
|
CHECK(std::hash<json> {}(json(two_63)) == std::hash<json> {}(json(9223372036854775808.0)));
|
|
CHECK(json(static_cast<json::number_unsigned_t>(int_max)) == json(int_max));
|
|
CHECK(std::hash<json> {}(json(static_cast<json::number_unsigned_t>(int_max))) == std::hash<json> {}(json(int_max)));
|
|
}
|
|
|
|
TEST_CASE("hash<nlohmann::ordered_json>")
|
|
{
|
|
// Collect hashes for different JSON values and make sure that they are distinct
|
|
// We cannot compare against fixed values, because the implementation of
|
|
// std::hash may differ between compilers.
|
|
|
|
std::set<std::size_t> hashes;
|
|
|
|
// null
|
|
hashes.insert(std::hash<ordered_json> {}(ordered_json(nullptr)));
|
|
|
|
// boolean
|
|
hashes.insert(std::hash<ordered_json> {}(ordered_json(true)));
|
|
hashes.insert(std::hash<ordered_json> {}(ordered_json(false)));
|
|
|
|
// string
|
|
hashes.insert(std::hash<ordered_json> {}(ordered_json("")));
|
|
hashes.insert(std::hash<ordered_json> {}(ordered_json("foo")));
|
|
|
|
// number
|
|
hashes.insert(std::hash<ordered_json> {}(ordered_json(0)));
|
|
hashes.insert(std::hash<ordered_json> {}(ordered_json(-1)));
|
|
hashes.insert(std::hash<ordered_json> {}(ordered_json(42.23)));
|
|
|
|
// array
|
|
hashes.insert(std::hash<ordered_json> {}(ordered_json::array()));
|
|
hashes.insert(std::hash<ordered_json> {}(ordered_json::array({1, 2, 3})));
|
|
|
|
// object
|
|
hashes.insert(std::hash<ordered_json> {}(ordered_json::object()));
|
|
hashes.insert(std::hash<ordered_json> {}(ordered_json::object({{"foo", "bar"}})));
|
|
|
|
// binary
|
|
hashes.insert(std::hash<ordered_json> {}(ordered_json::binary({})));
|
|
hashes.insert(std::hash<ordered_json> {}(ordered_json::binary({}, 0)));
|
|
hashes.insert(std::hash<ordered_json> {}(ordered_json::binary({}, 42)));
|
|
hashes.insert(std::hash<ordered_json> {}(ordered_json::binary({1, 2, 3})));
|
|
hashes.insert(std::hash<ordered_json> {}(ordered_json::binary({1, 2, 3}, 0)));
|
|
hashes.insert(std::hash<ordered_json> {}(ordered_json::binary({1, 2, 3}, 42)));
|
|
|
|
// discarded
|
|
hashes.insert(std::hash<ordered_json> {}(ordered_json(ordered_json::value_t::discarded)));
|
|
|
|
CHECK(hashes.size() == 19);
|
|
|
|
CHECK(std::hash<ordered_json> {}(ordered_json(0)) == std::hash<ordered_json> {}(ordered_json(static_cast<unsigned>(0))));
|
|
CHECK(std::hash<ordered_json> {}(ordered_json(0)) == std::hash<ordered_json> {}(ordered_json(0.0)));
|
|
}
|
|
|
|
TEST_CASE("hash of deeply nested values")
|
|
{
|
|
SECTION("hashing past the descent bound computes the same values")
|
|
{
|
|
// every depth on either side of where the iterative path takes over
|
|
for (std::size_t depth = 0; depth <= (2 * nlohmann::detail::recursion_depth_limit()) + 10; ++depth)
|
|
{
|
|
CAPTURE(depth)
|
|
const auto arrays = nested<json>(depth, false);
|
|
const auto objects = nested<json>(depth, true);
|
|
const auto ordered = nested<ordered_json>(depth, true);
|
|
CHECK(std::hash<json> {}(arrays) == reference_hash(arrays));
|
|
CHECK(std::hash<json> {}(objects) == reference_hash(objects));
|
|
CHECK(std::hash<ordered_json> {}(ordered) == reference_hash(ordered));
|
|
}
|
|
}
|
|
|
|
SECTION("values nested too deeply for the call stack (#5545)")
|
|
{
|
|
// recursing once per level used to exhaust the call stack here; the
|
|
// values are only parsed and hashed, never copied or compared, since
|
|
// those recurse as well
|
|
const std::size_t depth = 100000;
|
|
for (const bool objects :
|
|
{
|
|
false, true
|
|
})
|
|
{
|
|
CAPTURE(objects)
|
|
const auto text = nested_text(depth, objects);
|
|
const auto a = json::parse(text);
|
|
const auto b = json::parse(text);
|
|
CHECK(std::hash<json> {}(a) == std::hash<json> {}(b));
|
|
|
|
const auto c = ordered_json::parse(text);
|
|
const auto d = ordered_json::parse(text);
|
|
CHECK(std::hash<ordered_json> {}(c) == std::hash<ordered_json> {}(d));
|
|
}
|
|
}
|
|
}
|