Files
json/tests/src/unit-custom-object-key-type.cpp
T
Niels Lohmann e32051125f Fix the build of develop: tests from #5997 and #5991 (#6019)
* Fix the build of develop: tests from #5997 and #5991

- unit-msgpack-cpp17: assign the result of json::parse in
  CHECK_THROWS_WITH_AS (GCC -Werror=unused-result)
- unit-json_view: failing_allocator::allocate is [[noreturn]]
  (clang -Wmissing-noreturn)
- unit-class_lexer: build the quoted string once
  (clang-tidy performance-inefficient-string-concatenation)
- unit-allocator: sweep_allocator provides pointer, reference and size
  typedefs, which GCC 4.8's std::map takes from the allocator
- unit-allocator: skip the "sax_parse into an existing value" sweep with
  iterator debugging, like the parser callback sweep: MSVC's std::vector
  default constructor is noexcept but allocates its debug proxy through
  the allocator, so a failing allocation in create<array_t>() terminates

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix the remaining CI failures of the tests from #5997 and #5991

- unit-custom-object-key-type: drop the redundant member initializer and
  return a braced init list (clang-tidy); direct-initialize the converted
  value, as copy-initialization makes GCC 4.8 instantiate the copy
  constructor, which needs a range constructor strict_object lacks
- unit-stream-bulk-scan: pick the claimed get area size by tag dispatch
  instead of a constant condition (MSVC C4127)
- unit-allocator: skip the parser callback and sax_parse sweeps with
  Clang 3.4 and 3.5, which leak memory when an element constructor throws
  inside a libstdc++ std::map copy or std::vector reallocation

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix the CI failures that the first failing TU of each job hid

- unit-stream-bulk-scan: compute the claimed size without a condition;
  the tag-dispatched template was unused with a 64-bit std::size_t
  (-Wunused-template)
- unit-json_view: give failing_allocator the typedefs, rebind and
  members that GCC 4.8's std::map takes from the allocator directly
- clang-tidy hits in the tests from #5997: raw string literals
  (unit-json_view), redundant c_str() and a NOLINT for &text[0]
  (unit-locale-cpp), reserve() before a loop (unit-ordered_json), and a
  NOLINT for a return that a braced list would turn into an array
  (unit-regression1)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Skip the 3 GiB get-area test with a 32-bit std::size_t in the preprocessor

The claimed size was a constant 0 on Win32, so `claimed == 0 || ...` was a
constant condition and MSVC reported C4127. Guard the section with
`#if SIZE_MAX > UINT32_MAX` instead, as unit-msgpack-custom-types.cpp does.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Silence modernize-use-transparent-functors for failing_map in unit-json_view

std::less<> needs C++14, and the test is also built as C++11.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-03 13:42:46 +01:00

293 lines
9.6 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>
#include <cstddef>
#include <cstdint>
#include <map>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "custom_object_key_type.hpp"
// These tests instantiate a second basic_json specialization. They live in
// their own file rather than in unit-cbor.cpp and unit-msgpack.cpp to keep
// those objects below 65535 sections: the MinGW linker stores the section a
// COMDAT section is associated with in 16 bits, so it misplaces the jump
// tables of larger objects (see the clang job in windows.yml).
TEST_CASE("CBOR supports custom object key types")
{
using custom_json = custom_object_key_test::json;
using custom_key = custom_object_key_test::key;
custom_json::object_t object;
object.emplace(custom_key{"short"}, 1);
object.emplace(
custom_key{"a key longer than twenty-three characters"},
2);
const custom_json value(std::move(object));
const auto encoded = custom_json::to_cbor(value);
CHECK(nlohmann::json::from_cbor(encoded) == nlohmann::json
{
{"short", 1},
{"a key longer than twenty-three characters", 2}
});
}
TEST_CASE("CBOR supports custom object key types nested deeper than the recursion depth limit")
{
// below detail::recursion_depth_limit(), keys are written by
// write_cbor_iterative instead of write_cbor
using custom_json = custom_object_key_test::json;
using custom_key = custom_object_key_test::key;
const std::size_t depth = nlohmann::detail::recursion_depth_limit() + 10;
custom_json value = 1;
nlohmann::json expected = 1;
for (std::size_t i = 0; i < depth; ++i)
{
// alternate short keys with ones long enough to need a length byte
const std::string name = (i % 2 == 0) ? "k" + std::to_string(i)
: "a key longer than thirty-one characters " + std::to_string(i);
custom_json::object_t object;
object.emplace(custom_key{name}, std::move(value));
value = custom_json(std::move(object));
nlohmann::json::object_t expected_object;
expected_object.emplace(name, std::move(expected));
expected = nlohmann::json(std::move(expected_object));
}
const auto encoded = custom_json::to_cbor(value);
CHECK(encoded == nlohmann::json::to_cbor(expected));
CHECK(nlohmann::json::from_cbor(encoded) == expected);
}
TEST_CASE("MessagePack supports custom object key types")
{
using custom_json = custom_object_key_test::json;
using custom_key = custom_object_key_test::key;
custom_json::object_t object;
object.emplace(custom_key{"short"}, 1);
object.emplace(
custom_key{"a key longer than thirty-one characters"},
2);
const custom_json value(std::move(object));
const auto encoded = custom_json::to_msgpack(value);
CHECK(nlohmann::json::from_msgpack(encoded) == nlohmann::json
{
{"short", 1},
{"a key longer than thirty-one characters", 2}
});
}
TEST_CASE("MessagePack supports custom object key types nested deeper than the recursion depth limit")
{
// below detail::recursion_depth_limit(), keys are written by
// write_msgpack_iterative instead of write_msgpack
using custom_json = custom_object_key_test::json;
using custom_key = custom_object_key_test::key;
const std::size_t depth = nlohmann::detail::recursion_depth_limit() + 10;
custom_json value = 1;
nlohmann::json expected = 1;
for (std::size_t i = 0; i < depth; ++i)
{
// alternate short keys with ones long enough to need a length byte
const std::string name = (i % 2 == 0) ? "k" + std::to_string(i)
: "a key longer than thirty-one characters " + std::to_string(i);
custom_json::object_t object;
object.emplace(custom_key{name}, std::move(value));
value = custom_json(std::move(object));
nlohmann::json::object_t expected_object;
expected_object.emplace(name, std::move(expected));
expected = nlohmann::json(std::move(expected_object));
}
const auto encoded = custom_json::to_msgpack(value);
CHECK(encoded == nlohmann::json::to_msgpack(expected));
CHECK(nlohmann::json::from_msgpack(encoded) == expected);
}
namespace
{
// A key type that cannot be constructed from a std::string, so that a value
// with such keys cannot be converted from a nlohmann::json by the range
// constructor of its object type: the serializer converts the object instead.
class strict_key
{
public:
strict_key(const char* value)
: m_value(value)
{}
strict_key(const std::string& /*value*/) = delete;
#if JSON_DIAGNOSTICS
// Required by JSON_DIAGNOSTICS, which reads object keys through data()
// when building the path of an exception.
const char* data() const noexcept
{
return m_value.data();
}
#endif
friend bool operator<(const strict_key& lhs, const strict_key& rhs)
{
return lhs.m_value < rhs.m_value;
}
private:
std::string m_value;
};
strict_key make_strict_key(const std::string& key)
{
return {key.c_str()};
}
template<class Key, class Value, class Compare, class Allocator>
class strict_object
: public std::map <
strict_key,
Value,
std::less<strict_key>, // NOLINT(modernize-use-transparent-functors)
typename std::allocator_traits<Allocator>::template rebind_alloc <
std::pair<const strict_key, Value >>>
{
public:
strict_object() = default;
};
template<typename T, typename = void>
struct strict_serializer : nlohmann::adl_serializer<T> {};
using strict_json = nlohmann::basic_json < strict_object, std::vector, std::string, bool, std::int64_t,
std::uint64_t, double, std::allocator, strict_serializer, std::vector<std::uint8_t >>;
// the members of an object are converted one by one, their keys by make_strict_key()
template<>
struct strict_serializer<nlohmann::json::object_t, void>
{
static void to_json(strict_json& j, const nlohmann::json::object_t& object)
{
strict_json::object_t converted;
for (const auto& member : object)
{
converted.emplace(make_strict_key(member.first), strict_json(member.second));
}
j = strict_json(std::move(converted));
}
};
} // namespace
TEST_CASE("converting a value with keys that cannot be made from a std::string nested deeper than the recursion depth limit")
{
static_assert(!std::is_constructible<strict_key, const std::string&>::value, "the keys cannot be made from a std::string");
// below detail::recursion_depth_limit(), objects and arrays are converted
// by the serializers instead of the range constructors
const std::size_t depth = nlohmann::detail::recursion_depth_limit() + 10;
nlohmann::json value = 1;
for (std::size_t i = 0; i < depth; ++i)
{
if (i % 2 == 0)
{
value = nlohmann::json::object({{"a", std::move(value)}, {"b", true}});
}
else
{
value = nlohmann::json::array({std::move(value), "sibling"});
}
}
// direct-initialization: a copy-initialization makes GCC 4.8 instantiate the
// copy constructor, which needs a range constructor strict_object lacks
const strict_json converted(value);
// at() needs to make a std::string of a key for its error message
const auto member = [](const strict_json & object, const char* name) -> const strict_json &
{
const auto it = object.find(strict_key(name));
REQUIRE(it != object.end());
return *it;
};
// walk down the nesting levels without recursion
const strict_json* current = &converted;
for (std::size_t i = depth; i > 0; --i)
{
if (i % 2 == 1)
{
REQUIRE(current->is_object());
CHECK(current->size() == 2);
CHECK(member(*current, "b").get<bool>());
current = &member(*current, "a");
}
else
{
REQUIRE(current->is_array());
CHECK(current->size() == 2);
CHECK(current->at(1).get<std::string>() == "sibling");
current = &current->at(0);
}
}
CHECK(current->get<int>() == 1);
}
namespace
{
// shorter keys come first
struct shortlex_less
{
bool operator()(const std::string& lhs, const std::string& rhs) const
{
return lhs.size() != rhs.size() ? lhs.size() < rhs.size() : lhs < rhs;
}
};
template<class Key, class Value, class Compare, class Allocator>
using shortlex_map = std::map<Key, Value, shortlex_less, Allocator>;
using shortlex_json = nlohmann::json::with_object_t<shortlex_map>;
} // namespace
TEST_CASE("unflatten with an object type that does not sort the keys alphabetically")
{
// the keys of /a come before and after one of /b: unlike in an object
// sorted alphabetically, the values below a prefix are not found one
// after the other, so the value of /a must be looked up again
shortlex_json flat = shortlex_json::object();
flat["/a/p"] = 1;
flat["/b/qq"] = 2;
flat["/a/pqr"] = 3;
REQUIRE(flat.begin().key() == "/a/p");
REQUIRE((++flat.begin()).key() == "/b/qq");
CHECK(flat.unflatten().dump() == R"({"a":{"p":1,"pqr":3},"b":{"qq":2}})");
CHECK(flat.unflatten().flatten() == flat);
}