mirror of
https://github.com/nlohmann/json.git
synced 2026-09-27 22:05:54 +01:00
* test: cover JSON_NO_IO, JSON_THROW/TRY/CATCH_USER, JSON_SKIP_LIBRARY_VERSION_CHECK, and JSON_DisableEnumSerialization in CI (#5423) These four supported configuration macros were never actually compiled anywhere in the test matrix: - JSON_NO_IO and the JSON_THROW_USER/JSON_TRY_USER/JSON_CATCH_USER trio are exercised together in a new tests/src/unit-no_io_and_user_exceptions.cpp, which is automatically picked up by the existing unit-*.cpp test glob and thus built across the whole standard test matrix. - JSON_SKIP_LIBRARY_VERSION_CHECK is exercised by a new, dedicated tests/src/skip_library_version_check.cpp, compiled directly by the new ci_test_skiplibraryversioncheck target in cmake/ci.cmake: the scenario it simulates (mixing two differently-versioned inclusions of the library) unavoidably triggers the compiler's own "macro redefined" warning, which would fail under the library's own -Weverything/-Werror unit test matrix for a reason unrelated to the macro under test. - JSON_DisableEnumSerialization already had #if-guarded tests in several unit-*.cpp files (from #4384), but no CMake target ever actually set the JSON_DisableEnumSerialization CMake option, so that guarded code was never compiled. Add ci_test_disableenumserialization, mirroring the existing ci_test_noimplicitconversions/ci_test_noglobaludls targets. Building the full test suite with this option on surfaced one real, narrow gap: get<T>() on std::vector<std::byte> (used by unit-regression2.cpp's custom BinaryType tests) relies on std::byte being handled via enum serialization, so add the same #if-guard convention to the two affected SECTIONs there. Both new CI targets are added to the ci_cmake_options matrix in .github/workflows/ubuntu.yml, alongside the existing ci_test_* targets. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * test: cover multi-digit widths and bare alignment in std::formatter<json> (#5423) Every existing std::formatter spec with a width used a single digit (e.g. "{:2}"), so the width-parsing loop's accumulation of a second/third digit was never exercised; add multi-digit width cases. Likewise, every existing spec with an alignment character also had an explicit fill character, so the bare-alignment branch (e.g. "{:<}", with no fill) was never exercised; add cases asserting it keeps the default space indent character. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * test: add coverage for patch_inplace() (#5423) patch_inplace() had no unit test at all. Add a happy-path case mirroring an existing patch() example, and -- more importantly -- pin its distinguishing contract versus patch(): when a multi-operation JSON Patch fails partway through, patch_inplace() (which mutates the document directly, operation by operation) leaves whatever operations already succeeded applied, whereas patch() (which applies the patch to an internal copy that is discarded on exception) leaves the original completely untouched either way. Verified empirically against the current implementation before writing the assertions. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * test: fix duplicate TEST_CASE name in unit-no-mem-leak-on-adl-serialize.cpp (#5423) Two distinct TEST_CASEs were both named "check_for_mem_leak_on_adl_to_json-2". doctest allows duplicate names, so both still ran, but it makes --test-case=<name> filtering and reporting ambiguous. Rename the second one to "-3", continuing the existing "-1"/"-2" sequence. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * test: add direct coverage for the std::u8string to_json overload (#5423) The ADL to_json overload for std::basic_string<char8_t, ...> was only ever reached indirectly, via std::filesystem::path::u8string(). Add a test that constructs a json value directly from a std::u8string, gated the same way as the overload itself (include/nlohmann/detail/conversions/to_json.hpp): behind both the std::filesystem::path feature guard and __cpp_lib_char8_t, since the overload only exists when both are satisfied. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * test: verify move semantics of byte_container_with_subtype's rvalue constructors (#5423) The two rvalue-reference constructors were never distinguished from their const-lvalue-reference twins by any test. Add a "move semantics" section that constructs from an rvalue std::vector, checks the resulting container keeps the exact same buffer address as the source (a stronger check than just observing the source ended up empty, since a copy-then-clear could do that too), and confirms the source vector was left empty. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Guard patch_inplace() partial-application test against JSON_NOEXCEPTION The "distinguishing contract vs patch(): partial application on failure" test relies on doc.patch_inplace(patch) actually throwing so the partially-applied state can be observed right after the throw point. Under ci_test_noexceptions, JSON_THROW() calls std::abort() instead of throwing, and doctest's --no-throw test filter (which that CI job passes) makes CHECK_THROWS_AS() a no-op that never even evaluates its expression -- so patch_inplace() is never called and the follow-up assertions fail against the untouched original document. Guard the whole SECTION with #if !defined(JSON_NOEXCEPTION), following the same convention already used elsewhere in the test suite (e.g. unit-class_parser.cpp) for exception-dependent tests. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix MSVC C2220 in the std::u8string conversion test MSVC's C5321 ("nonstandard extension used: encoding '\xNN' as a multi-byte utf-8 character") is promoted to a hard error by our MSVC CI configs. It fires because the test composed a non-ASCII UTF-8 sequence inside a u8"" literal using raw \x byte escapes; MSVC treats that as nonstandard and suggests using \u universal-character-names instead, which every compiler agrees on and which compiles down to the exact same encoded bytes. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Guard the JSON_THROW_USER test against JSON_NOEXCEPTION and GCC's -Wunused-result Two independent CI configurations failed to build/run this new test: - ci_test_noexceptions runs the whole suite with -DJSON_NOEXCEPTION and doctest's "--no-throw" filter, which compiles CHECK_THROWS_AS() down to a no-op that never even invokes the guarded expression. Since this test's whole point is to observe json_throw_user_call_count after json::parse()/at() actually throw, it can't be meaningfully run under that filter (our JSON_THROW_USER override still throws real exceptions regardless of JSON_NOEXCEPTION, but the assertion never gets a chance to run). Guard the TEST_CASE with #if !defined(JSON_NOEXCEPTION), mirroring the existing precedent in unit-json_patch.cpp. - ci_test_gcc and ci_test_standards_gcc(11) failed with -Werror=unused-result on the discarded json::parse() return value. json::parse() is marked warn_unused_result, and unlike a real [[nodiscard]] attribute, GCC does not consider that satisfied by doctest's (void)-cast around the expression in C++11 mode. Assign the result to a discarded local instead, matching the established `json _ = json::parse(...)` idiom already used throughout unit-class_parser.cpp. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Suppress a clang-tidy false positive on an intentional defensive copy performance-unnecessary-copy-initialization suggests copy_for_patch could be a reference since it's never modified -- but the copy is the point: it guards against a hypothetical regression where patch() mutates its receiver, which a reference could never catch (the follow-up assertion would just compare `original` to itself). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix clang-tidy findings in the JSON_NO_IO/JSON_THROW_USER test - bugprone-macro-parentheses: wrap the JSON_THROW_USER macro argument in parentheses at the throw site. - modernize-raw-string-literal: switch two escaped JSON string literals to raw string literals. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
956 lines
32 KiB
C++
956 lines
32 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"
|
||
|
||
// for some reason including this after the json header leads to linker errors with VS 2017...
|
||
#include <locale>
|
||
|
||
// skip tests if JSON_DisableEnumSerialization=ON (#4384): std::byte is a
|
||
// scoped enum, so get<std::byte>() (needed below to get<std::vector<std::byte>>()
|
||
// from a plain JSON array, not just from an already-binary value) relies on
|
||
// enum serialization being enabled
|
||
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
|
||
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||
#endif
|
||
|
||
#define JSON_TESTS_PRIVATE
|
||
#include <nlohmann/json.hpp>
|
||
using json = nlohmann::json;
|
||
using ordered_json = nlohmann::ordered_json;
|
||
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||
#endif
|
||
|
||
#include <cstdio>
|
||
#include <deque>
|
||
#include <list>
|
||
#include <type_traits>
|
||
#include <utility>
|
||
|
||
#ifdef JSON_HAS_CPP_17
|
||
#include <any>
|
||
#include <variant>
|
||
#endif
|
||
|
||
#ifdef JSON_HAS_CPP_17
|
||
#if __has_include(<optional>)
|
||
#include <optional>
|
||
#elif __has_include(<experimental/optional>)
|
||
#endif
|
||
|
||
/////////////////////////////////////////////////////////////////////
|
||
// for #4804
|
||
/////////////////////////////////////////////////////////////////////
|
||
using json_4804 = nlohmann::basic_json<std::map, // ObjectType
|
||
std::vector, // ArrayType
|
||
std::string, // StringType
|
||
bool, // BooleanType
|
||
std::int64_t, // NumberIntegerType
|
||
std::uint64_t, // NumberUnsignedType
|
||
double, // NumberFloatType
|
||
std::allocator, // AllocatorType
|
||
nlohmann::adl_serializer, // JSONSerializer
|
||
std::vector<std::byte>, // BinaryType
|
||
void // CustomBaseClass
|
||
>;
|
||
#endif
|
||
|
||
#ifdef JSON_HAS_CPP_20
|
||
#if __has_include(<span>)
|
||
#include <span>
|
||
#endif
|
||
#endif
|
||
|
||
/////////////////////////////////////////////////////////////////////
|
||
// for #4825 - explicitly instantiating basic_json must compile; this
|
||
// forces instantiation of binary_writer::write_bjdata_ndarray, whose
|
||
// static_cast<string_t> was ambiguous under explicit instantiation on
|
||
// C++17. Merely compiling this translation unit is the regression test.
|
||
/////////////////////////////////////////////////////////////////////
|
||
template class nlohmann::basic_json<>;
|
||
|
||
/////////////////////////////////////////////////////////////////////
|
||
// for #4440
|
||
/////////////////////////////////////////////////////////////////////
|
||
#if JSON_HAS_RANGES == 1
|
||
#include <ranges>
|
||
#endif
|
||
|
||
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
|
||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||
/////////////////////////////////////////////////////////////////////
|
||
// for #3077
|
||
/////////////////////////////////////////////////////////////////////
|
||
|
||
class FooAlloc
|
||
{};
|
||
|
||
class Foo
|
||
{
|
||
public:
|
||
explicit Foo(const FooAlloc& /* unused */ = FooAlloc()) {}
|
||
|
||
bool value = false;
|
||
};
|
||
|
||
class FooBar
|
||
{
|
||
public:
|
||
Foo foo{}; // NOLINT(readability-redundant-member-init)
|
||
};
|
||
|
||
inline void from_json(const nlohmann::json& j, FooBar& fb) // NOLINT(misc-use-internal-linkage)
|
||
{
|
||
j.at("value").get_to(fb.foo.value);
|
||
}
|
||
|
||
/////////////////////////////////////////////////////////////////////
|
||
// for #3171
|
||
/////////////////////////////////////////////////////////////////////
|
||
|
||
struct for_3171_base // NOLINT(cppcoreguidelines-special-member-functions)
|
||
{
|
||
for_3171_base(const std::string& /*unused*/ = {}) {}
|
||
virtual ~for_3171_base();
|
||
|
||
for_3171_base(const for_3171_base& other) // NOLINT(hicpp-use-equals-default,modernize-use-equals-default)
|
||
: str(other.str)
|
||
{}
|
||
|
||
for_3171_base& operator=(const for_3171_base& other)
|
||
{
|
||
if (this != &other)
|
||
{
|
||
str = other.str;
|
||
}
|
||
return *this;
|
||
}
|
||
|
||
for_3171_base(for_3171_base&& other) noexcept
|
||
: str(std::move(other.str))
|
||
{}
|
||
|
||
for_3171_base& operator=(for_3171_base&& other) noexcept
|
||
{
|
||
if (this != &other)
|
||
{
|
||
str = std::move(other.str);
|
||
}
|
||
return *this;
|
||
}
|
||
|
||
virtual void _from_json(const json& j)
|
||
{
|
||
j.at("str").get_to(str);
|
||
}
|
||
|
||
std::string str{}; // NOLINT(readability-redundant-member-init)
|
||
};
|
||
|
||
for_3171_base::~for_3171_base() = default;
|
||
|
||
struct for_3171_derived : public for_3171_base
|
||
{
|
||
for_3171_derived() = default;
|
||
~for_3171_derived() override;
|
||
explicit for_3171_derived(const std::string& /*unused*/) { }
|
||
|
||
for_3171_derived(const for_3171_derived& other) // NOLINT(hicpp-use-equals-default,modernize-use-equals-default)
|
||
: for_3171_base(other)
|
||
{}
|
||
|
||
for_3171_derived& operator=(const for_3171_derived& other)
|
||
{
|
||
if (this != &other)
|
||
{
|
||
for_3171_base::operator=(other); // Call base class assignment operator
|
||
}
|
||
return *this;
|
||
}
|
||
|
||
for_3171_derived(for_3171_derived&& other) noexcept
|
||
: for_3171_base(std::move(other))
|
||
{}
|
||
|
||
for_3171_derived& operator=(for_3171_derived&& other) noexcept
|
||
{
|
||
if (this != &other)
|
||
{
|
||
for_3171_base::operator=(std::move(other)); // Call base class move assignment operator
|
||
}
|
||
return *this;
|
||
}
|
||
};
|
||
|
||
for_3171_derived::~for_3171_derived() = default;
|
||
|
||
inline void from_json(const json& j, for_3171_base& tb) // NOLINT(misc-use-internal-linkage)
|
||
{
|
||
tb._from_json(j);
|
||
}
|
||
|
||
/////////////////////////////////////////////////////////////////////
|
||
// for #3312
|
||
/////////////////////////////////////////////////////////////////////
|
||
|
||
#ifdef JSON_HAS_CPP_20
|
||
struct for_3312
|
||
{
|
||
std::string name;
|
||
};
|
||
|
||
inline void from_json(const json& j, for_3312& obj) // NOLINT(misc-use-internal-linkage)
|
||
{
|
||
j.at("name").get_to(obj.name);
|
||
}
|
||
#endif
|
||
|
||
/////////////////////////////////////////////////////////////////////
|
||
// for #3204
|
||
/////////////////////////////////////////////////////////////////////
|
||
|
||
struct for_3204_foo
|
||
{
|
||
for_3204_foo() = default;
|
||
explicit for_3204_foo(std::string /*unused*/) {} // NOLINT(performance-unnecessary-value-param)
|
||
};
|
||
|
||
struct for_3204_bar
|
||
{
|
||
enum constructed_from_t // NOLINT(cppcoreguidelines-use-enum-class)
|
||
{
|
||
constructed_from_none = 0,
|
||
constructed_from_foo = 1,
|
||
constructed_from_json = 2
|
||
};
|
||
|
||
explicit for_3204_bar(std::function<void(for_3204_foo)> /*unused*/) noexcept // NOLINT(performance-unnecessary-value-param)
|
||
: constructed_from(constructed_from_foo) {}
|
||
explicit for_3204_bar(std::function<void(json)> /*unused*/) noexcept // NOLINT(performance-unnecessary-value-param)
|
||
: constructed_from(constructed_from_json) {}
|
||
|
||
constructed_from_t constructed_from = constructed_from_none;
|
||
};
|
||
|
||
/////////////////////////////////////////////////////////////////////
|
||
// for #3333
|
||
/////////////////////////////////////////////////////////////////////
|
||
|
||
struct for_3333 final
|
||
{
|
||
for_3333(int x_ = 0, int y_ = 0) : x(x_), y(y_) {}
|
||
|
||
template <class T>
|
||
for_3333(const T& /*unused*/)
|
||
{
|
||
CHECK(false);
|
||
}
|
||
|
||
int x = 0;
|
||
int y = 0;
|
||
};
|
||
|
||
template <>
|
||
inline for_3333::for_3333(const json& j)
|
||
: for_3333(j.value("x", 0), j.value("y", 0))
|
||
{}
|
||
|
||
/////////////////////////////////////////////////////////////////////
|
||
// for #3810
|
||
/////////////////////////////////////////////////////////////////////
|
||
|
||
struct Example_3810
|
||
{
|
||
int bla{};
|
||
|
||
Example_3810() = default;
|
||
};
|
||
|
||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Example_3810, bla) // NOLINT(misc-use-internal-linkage)
|
||
|
||
/////////////////////////////////////////////////////////////////////
|
||
// for #4740
|
||
/////////////////////////////////////////////////////////////////////
|
||
|
||
#ifdef JSON_HAS_CPP_17
|
||
struct Example_4740
|
||
{
|
||
std::optional<std::string> host = std::nullopt;
|
||
std::optional<int> port = std::nullopt;
|
||
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Example_4740, host, port)
|
||
};
|
||
#endif
|
||
|
||
TEST_CASE("regression tests 3")
|
||
{
|
||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||
// JSON_HAS_CPP_17 (do not remove; see note at top of file)
|
||
SECTION("issue #3070 - Version 3.10.3 breaks backward-compatibility with 3.10.2 ")
|
||
{
|
||
nlohmann::detail::std_fs::path text_path("/tmp/text.txt");
|
||
const json j(text_path);
|
||
|
||
const auto j_path = j.get<nlohmann::detail::std_fs::path>();
|
||
CHECK(j_path == text_path);
|
||
|
||
#if DOCTEST_CLANG || DOCTEST_GCC >= DOCTEST_COMPILER(8, 4, 0)
|
||
// only known to work on Clang and GCC >=8.4
|
||
CHECK_THROWS_WITH_AS(nlohmann::detail::std_fs::path(json(1)), "[json.exception.type_error.302] type must be string, but is number", json::type_error);
|
||
#endif
|
||
}
|
||
#endif
|
||
|
||
SECTION("issue #3077 - explicit constructor with default does not compile")
|
||
{
|
||
json j;
|
||
j[0]["value"] = true;
|
||
std::vector<FooBar> foo;
|
||
j.get_to(foo);
|
||
}
|
||
|
||
SECTION("issue #3108 - ordered_json doesn't support range based erase")
|
||
{
|
||
ordered_json j = {1, 2, 2, 4};
|
||
|
||
auto last = std::unique(j.begin(), j.end());
|
||
j.erase(last, j.end());
|
||
|
||
CHECK(j.dump() == "[1,2,4]");
|
||
|
||
j.erase(std::remove_if(j.begin(), j.end(), [](const ordered_json & val)
|
||
{
|
||
return val == 2;
|
||
}), j.end());
|
||
|
||
CHECK(j.dump() == "[1,4]");
|
||
}
|
||
|
||
SECTION("issue #3343 - json and ordered_json are not interchangeable")
|
||
{
|
||
json::object_t jobj({ { "product", "one" } });
|
||
ordered_json::object_t ojobj({{"product", "one"}});
|
||
|
||
auto jit = jobj.begin();
|
||
auto ojit = ojobj.begin();
|
||
|
||
CHECK(jit->first == ojit->first);
|
||
CHECK(jit->second.get<std::string>() == ojit->second.get<std::string>());
|
||
}
|
||
|
||
SECTION("issue #3171 - if class is_constructible from std::string wrong from_json overload is being selected, compilation failed")
|
||
{
|
||
const json j{{ "str", "value"}};
|
||
|
||
// failed with: error: no match for ‘operator=’ (operand types are ‘for_3171_derived’ and ‘const nlohmann::basic_json<>::string_t’
|
||
// {aka ‘const std::__cxx11::basic_string<char>’})
|
||
// s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
|
||
auto td = j.get<for_3171_derived>();
|
||
|
||
CHECK(td.str == "value");
|
||
}
|
||
|
||
#ifdef JSON_HAS_CPP_20
|
||
SECTION("issue #3312 - Parse to custom class from unordered_json breaks on G++11.2.0 with C++20")
|
||
{
|
||
// see test for #3171
|
||
const ordered_json j = {{"name", "class"}};
|
||
for_3312 obj{};
|
||
|
||
j.get_to(obj);
|
||
|
||
CHECK(obj.name == "class");
|
||
}
|
||
#endif
|
||
|
||
#if defined(JSON_HAS_CPP_17) && JSON_USE_IMPLICIT_CONVERSIONS
|
||
SECTION("issue #3428 - Error occurred when converting nlohmann::json to std::any")
|
||
{
|
||
const json j;
|
||
const std::any a1 = j;
|
||
std::any&& a2 = j;
|
||
|
||
CHECK(a1.type() == typeid(j));
|
||
CHECK(a2.type() == typeid(j));
|
||
}
|
||
#endif
|
||
|
||
SECTION("issue #3204 - ambiguous regression")
|
||
{
|
||
const for_3204_bar bar_from_foo([](for_3204_foo) noexcept {}); // NOLINT(performance-unnecessary-value-param)
|
||
const for_3204_bar bar_from_json([](json) noexcept {}); // NOLINT(performance-unnecessary-value-param)
|
||
|
||
CHECK(bar_from_foo.constructed_from == for_3204_bar::constructed_from_foo);
|
||
CHECK(bar_from_json.constructed_from == for_3204_bar::constructed_from_json);
|
||
}
|
||
|
||
SECTION("issue #3333 - Ambiguous conversion from nlohmann::basic_json<> to custom class")
|
||
{
|
||
const json j
|
||
{
|
||
{"x", 1},
|
||
{"y", 2}
|
||
};
|
||
const for_3333 p = j;
|
||
|
||
CHECK(p.x == 1);
|
||
CHECK(p.y == 2);
|
||
}
|
||
|
||
SECTION("issue #3810 - ordered_json doesn't support construction from C array of custom type")
|
||
{
|
||
Example_3810 states[45]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||
|
||
// fix "not used" warning
|
||
states[0].bla = 1;
|
||
|
||
const auto* const expected = R"([{"bla":1},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0}])";
|
||
|
||
// This works:
|
||
nlohmann::json j;
|
||
j["test"] = states;
|
||
CHECK(j["test"].dump() == expected);
|
||
|
||
// This doesn't compile:
|
||
nlohmann::ordered_json oj;
|
||
oj["test"] = states;
|
||
CHECK(oj["test"].dump() == expected);
|
||
}
|
||
|
||
#ifdef JSON_HAS_CPP_17
|
||
SECTION("issue #4740 - build issue with std::optional")
|
||
{
|
||
const auto t1 = Example_4740();
|
||
const auto j1 = nlohmann::json(t1);
|
||
CHECK(j1.dump() == "{\"host\":null,\"port\":null}");
|
||
const auto t2 = j1.get<Example_4740>();
|
||
CHECK(!t2.host.has_value());
|
||
CHECK(!t2.port.has_value());
|
||
|
||
// improve coverage
|
||
auto t3 = Example_4740();
|
||
t3.port = 80;
|
||
t3.host = "example.com";
|
||
const auto j2 = nlohmann::json(t3);
|
||
CHECK(j2.dump() == "{\"host\":\"example.com\",\"port\":80}");
|
||
const auto t4 = j2.get<Example_4740>();
|
||
CHECK(t4.host.has_value());
|
||
CHECK(t4.port.has_value());
|
||
}
|
||
#endif
|
||
|
||
#if !defined(_MSVC_LANG)
|
||
// MSVC returns garbage on invalid enum values, so this test is excluded
|
||
// there.
|
||
SECTION("issue #4762 - json exception 302 with unhelpful explanation : type must be number, but is number")
|
||
{
|
||
// In #4762, the main issue was that a json object with an invalid type
|
||
// returned "number" as type_name(), because this was the default case.
|
||
// This test makes sure we now return "invalid" instead.
|
||
json j;
|
||
j.m_data.m_type = static_cast<json::value_t>(100); // NOLINT(clang-analyzer-optin.core.EnumCastOutOfRange)
|
||
CHECK(j.type_name() == "invalid");
|
||
}
|
||
#endif
|
||
|
||
#ifdef JSON_HAS_CPP_17
|
||
SECTION("issue #4804: from_cbor incompatible with std::vector<std::byte> as binary_t")
|
||
{
|
||
const std::vector<std::uint8_t> data = {0x80};
|
||
const auto decoded = json_4804::from_cbor(data);
|
||
CHECK((decoded == json_4804::array()));
|
||
}
|
||
|
||
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||
SECTION("discussion #4209 - custom BinaryType direct assignment and round-tripping")
|
||
{
|
||
// Test that assigning a custom BinaryType directly creates a binary value, not an array
|
||
const std::vector<std::byte> original{std::byte{1}, std::byte{2}, std::byte{3}};
|
||
const json_4804 j = original;
|
||
CHECK(j.is_binary());
|
||
CHECK(!j.is_array());
|
||
|
||
// Test round-tripping: extracting the binary value back as the custom container type
|
||
const auto extracted = j.get<std::vector<std::byte>>();
|
||
CHECK(extracted == original);
|
||
|
||
// Test that the default json alias behavior is unchanged: std::vector<uint8_t> -> array
|
||
const json default_json = std::vector<std::uint8_t> {1, 2, 3};
|
||
CHECK(default_json.is_array());
|
||
CHECK(!default_json.is_binary());
|
||
}
|
||
|
||
SECTION("discussion #4209 - custom BinaryType extraction from parsed array")
|
||
{
|
||
// Test that extracting a custom BinaryType from a parsed JSON array still works
|
||
// (not just from a binary-typed node)
|
||
const auto j = json_4804::parse("[1,2,3]");
|
||
CHECK(j.is_array());
|
||
CHECK(!j.is_binary());
|
||
|
||
// Extracting as custom BinaryType should work from arrays
|
||
const auto extracted = j.get<std::vector<std::byte>>();
|
||
CHECK(extracted.size() == 3);
|
||
CHECK(extracted[0] == std::byte{1});
|
||
CHECK(extracted[1] == std::byte{2});
|
||
CHECK(extracted[2] == std::byte{3});
|
||
}
|
||
#endif
|
||
|
||
SECTION("issue #5046 - implicit conversion of return json to std::optional no longer implicit")
|
||
{
|
||
const json jval{};
|
||
auto GetValue = [](const json & valRoot) -> std::optional<json>
|
||
{
|
||
if (valRoot.contains("default"))
|
||
{
|
||
return valRoot.at("default");
|
||
}
|
||
return std::nullopt;
|
||
};
|
||
auto result = GetValue(jval);
|
||
CHECK(!result.has_value());
|
||
}
|
||
#endif
|
||
|
||
#if JSON_HAS_RANGES == 1
|
||
SECTION("issue #4440 - assert when using std::views::filter and GCC 10")
|
||
{
|
||
auto noOpFilter = std::views::filter([](auto&&) noexcept
|
||
{
|
||
return true;
|
||
});
|
||
json j = {1, 2, 3};
|
||
auto filtered = j | noOpFilter;
|
||
CHECK(*filtered.begin() == 1);
|
||
}
|
||
#endif
|
||
|
||
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
||
SECTION("issue #4916 - constructing array from C++20 ranges view does not work")
|
||
{
|
||
std::vector<int> nums{1, 2, 37, 42, 21};
|
||
auto filteredNums = nums | std::views::filter([](int i)
|
||
{
|
||
return i > 10;
|
||
});
|
||
json const j(filteredNums);
|
||
CHECK(j.type() == json::value_t::array);
|
||
CHECK(j == json({37, 42, 21}));
|
||
}
|
||
#endif
|
||
|
||
// owning_view is not available in libstdc++ < 12
|
||
#if JSON_HAS_RANGES && !defined(__MINGW32__) && !(defined(__GLIBCXX__) && _GLIBCXX_RELEASE < 12)
|
||
SECTION("issue #4916 - constructing array from prvalue C++20 ranges view (owning_view)")
|
||
{
|
||
json const j(std::vector<int> {1, 2, 37, 42, 21} | std::views::filter([](int i)
|
||
{
|
||
return i > 10;
|
||
}));
|
||
CHECK(j.type() == json::value_t::array);
|
||
CHECK(j == json({37, 42, 21}));
|
||
}
|
||
#endif
|
||
|
||
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
||
SECTION("issue #4916 - constructing array from C++20 transform view (prvalue elements)")
|
||
{
|
||
std::vector<int> nums{1, 2, 3};
|
||
auto t = nums | std::views::transform([](int i) noexcept
|
||
{
|
||
return i * 2;
|
||
});
|
||
json const j(t);
|
||
CHECK(j.type() == json::value_t::array);
|
||
CHECK(j == json({2, 4, 6}));
|
||
}
|
||
#endif
|
||
}
|
||
|
||
TEST_CASE_TEMPLATE("issue #4798 - nlohmann::json::to_msgpack() encode float NaN as double", T, double, float) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
||
{
|
||
// With issue #4798, we encode NaN, infinity, and -infinity as float instead
|
||
// of double to allow for smaller encodings.
|
||
const json jx = std::numeric_limits<T>::quiet_NaN();
|
||
const json jy = std::numeric_limits<T>::infinity();
|
||
const json jz = -std::numeric_limits<T>::infinity();
|
||
|
||
/////////////////////////////////////////////////////////////////////////
|
||
// MessagePack
|
||
/////////////////////////////////////////////////////////////////////////
|
||
|
||
// expected MessagePack values
|
||
const std::vector<std::uint8_t> msgpack_x = {{0xCA, 0x7F, 0xC0, 0x00, 0x00}};
|
||
const std::vector<std::uint8_t> msgpack_y = {{0xCA, 0x7F, 0x80, 0x00, 0x00}};
|
||
const std::vector<std::uint8_t> msgpack_z = {{0xCA, 0xFF, 0x80, 0x00, 0x00}};
|
||
|
||
CHECK(json::to_msgpack(jx) == msgpack_x);
|
||
CHECK(json::to_msgpack(jy) == msgpack_y);
|
||
CHECK(json::to_msgpack(jz) == msgpack_z);
|
||
|
||
CHECK(std::isnan(json::from_msgpack(msgpack_x).get<T>()));
|
||
CHECK(json::from_msgpack(msgpack_y).get<T>() == std::numeric_limits<T>::infinity());
|
||
CHECK(json::from_msgpack(msgpack_z).get<T>() == -std::numeric_limits<T>::infinity());
|
||
|
||
// Make sure the other MessagePakc encodings for NaN, infinity, and
|
||
// -infinity are still supported.
|
||
const std::vector<std::uint8_t> msgpack_x_2 = {{0xCB, 0x7F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||
const std::vector<std::uint8_t> msgpack_y_2 = {{0xCB, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||
const std::vector<std::uint8_t> msgpack_z_2 = {{0xCB, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||
CHECK(std::isnan(json::from_msgpack(msgpack_x_2).get<T>()));
|
||
CHECK(json::from_msgpack(msgpack_y_2).get<T>() == std::numeric_limits<T>::infinity());
|
||
CHECK(json::from_msgpack(msgpack_z_2).get<T>() == -std::numeric_limits<T>::infinity());
|
||
|
||
/////////////////////////////////////////////////////////////////////////
|
||
// CBOR
|
||
/////////////////////////////////////////////////////////////////////////
|
||
|
||
// expected CBOR values
|
||
const std::vector<std::uint8_t> cbor_x = {{0xF9, 0x7E, 0x00}};
|
||
const std::vector<std::uint8_t> cbor_y = {{0xF9, 0x7C, 0x00}};
|
||
const std::vector<std::uint8_t> cbor_z = {{0xF9, 0xfC, 0x00}};
|
||
|
||
CHECK(json::to_cbor(jx) == cbor_x);
|
||
CHECK(json::to_cbor(jy) == cbor_y);
|
||
CHECK(json::to_cbor(jz) == cbor_z);
|
||
|
||
CHECK(std::isnan(json::from_cbor(cbor_x).get<T>()));
|
||
CHECK(json::from_cbor(cbor_y).get<T>() == std::numeric_limits<T>::infinity());
|
||
CHECK(json::from_cbor(cbor_z).get<T>() == -std::numeric_limits<T>::infinity());
|
||
|
||
// Make sure the other CBOR encodings for NaN, infinity, and -infinity are
|
||
// still supported.
|
||
const std::vector<std::uint8_t> cbor_x_2 = {{0xFA, 0x7F, 0xC0, 0x00, 0x00}};
|
||
const std::vector<std::uint8_t> cbor_y_2 = {{0xFA, 0x7F, 0x80, 0x00, 0x00}};
|
||
const std::vector<std::uint8_t> cbor_z_2 = {{0xFA, 0xFF, 0x80, 0x00, 0x00}};
|
||
const std::vector<std::uint8_t> cbor_x_3 = {{0xFB, 0x7F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||
const std::vector<std::uint8_t> cbor_y_3 = {{0xFB, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||
const std::vector<std::uint8_t> cbor_z_3 = {{0xFB, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||
CHECK(std::isnan(json::from_cbor(cbor_x_2).get<T>()));
|
||
CHECK(json::from_cbor(cbor_y_2).get<T>() == std::numeric_limits<T>::infinity());
|
||
CHECK(json::from_cbor(cbor_z_2).get<T>() == -std::numeric_limits<T>::infinity());
|
||
CHECK(std::isnan(json::from_cbor(cbor_x_3).get<T>()));
|
||
CHECK(json::from_cbor(cbor_y_3).get<T>() == std::numeric_limits<T>::infinity());
|
||
CHECK(json::from_cbor(cbor_z_3).get<T>() == -std::numeric_limits<T>::infinity());
|
||
}
|
||
|
||
TEST_CASE("regression test #5074 - portable workaround for single-element brace init")
|
||
{
|
||
json const j_obj = {{"key", "value"}};
|
||
|
||
json const j = json::array({j_obj});
|
||
CHECK(j.is_array());
|
||
CHECK(j.size() == 1);
|
||
CHECK(j[0] == j_obj);
|
||
}
|
||
|
||
#if defined(JSON_BRACE_INIT_COPY_SEMANTICS) && (JSON_BRACE_INIT_COPY_SEMANTICS == 1)
|
||
TEST_CASE("regression test #5074 - single-element brace init with JSON_BRACE_INIT_COPY_SEMANTICS")
|
||
{
|
||
// with JSON_BRACE_INIT_COPY_SEMANTICS: single-element brace init copies/moves
|
||
json const j_obj = {{"key", "value"}, {"num", 42}};
|
||
json const j_arr = {1, 2, 3};
|
||
|
||
// object: brace init copies instead of wrapping
|
||
json const j1{j_obj};
|
||
CHECK(j1.is_object());
|
||
CHECK(j1 == j_obj);
|
||
|
||
// array: brace init copies instead of wrapping
|
||
json const j2{j_arr};
|
||
CHECK(j2.is_array());
|
||
CHECK(j2.size() == 3);
|
||
CHECK(j2 == j_arr);
|
||
|
||
// primitives still work as initializer lists
|
||
json const j3{true};
|
||
CHECK(j3.is_boolean());
|
||
|
||
json const j4{42};
|
||
CHECK(j4.is_number_integer());
|
||
}
|
||
#endif
|
||
|
||
struct Example_5122
|
||
{
|
||
float b = 2;
|
||
nlohmann::ordered_map<std::string, std::string> c{}; // NOLINT(readability-redundant-member-init): needed for GCC -Weffc++
|
||
int a = 1;
|
||
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Example_5122, b, c, a)
|
||
};
|
||
|
||
TEST_CASE("regression test #5122 - from_json into types holding nlohmann::ordered_map")
|
||
{
|
||
Example_5122 src;
|
||
src.c.emplace("first", "1");
|
||
src.c.emplace("second", "2");
|
||
|
||
ordered_json const j = src;
|
||
Example_5122 const dst = j.get<Example_5122>();
|
||
|
||
CHECK(dst.b == src.b);
|
||
CHECK(dst.a == src.a);
|
||
REQUIRE(dst.c.size() == src.c.size());
|
||
auto src_it = src.c.begin();
|
||
auto dst_it = dst.c.begin();
|
||
for (; src_it != src.c.end(); ++src_it, ++dst_it)
|
||
{
|
||
CHECK(dst_it->first == src_it->first);
|
||
CHECK(dst_it->second == src_it->second);
|
||
}
|
||
}
|
||
|
||
// -Wself-assign-overloaded was introduced in Clang 7. Gate the pragma on
|
||
// __has_warning so older Clang versions do not error with "unknown warning
|
||
// group". The __has_warning check has to stay inside the __clang__ branch
|
||
// because GCC does not provide it and would tokenize-error on the argument.
|
||
#if defined(__clang__) && defined(__has_warning)
|
||
#if __has_warning("-Wself-assign-overloaded")
|
||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wself-assign-overloaded")
|
||
#endif
|
||
#endif
|
||
|
||
TEST_CASE("regression test #5122 - nlohmann::ordered_map copy-assignment is self-assignment safe")
|
||
{
|
||
nlohmann::ordered_map<std::string, std::string> m;
|
||
m.emplace("first", "1");
|
||
m.emplace("second", "2");
|
||
|
||
// Insertion order is preserved by ordered_map, so we can check it directly.
|
||
m = m;
|
||
|
||
REQUIRE(m.size() == 2);
|
||
auto it = m.begin();
|
||
CHECK(it->first == "first");
|
||
CHECK(it->second == "1");
|
||
++it;
|
||
CHECK(it->first == "second");
|
||
CHECK(it->second == "2");
|
||
}
|
||
|
||
#if defined(__clang__) && defined(__has_warning)
|
||
#if __has_warning("-Wself-assign-overloaded")
|
||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||
#endif
|
||
#endif
|
||
|
||
TEST_CASE("regression test #5122 - nlohmann::ordered_map move-assignment transfers contents")
|
||
{
|
||
nlohmann::ordered_map<std::string, std::string> src;
|
||
src.emplace("first", "1");
|
||
src.emplace("second", "2");
|
||
|
||
nlohmann::ordered_map<std::string, std::string> dst;
|
||
dst.emplace("stale", "x");
|
||
dst = std::move(src);
|
||
|
||
REQUIRE(dst.size() == 2);
|
||
auto it = dst.begin();
|
||
CHECK(it->first == "first");
|
||
CHECK(it->second == "1");
|
||
++it;
|
||
CHECK(it->first == "second");
|
||
CHECK(it->second == "2");
|
||
|
||
// Re-assigning into the moved-from object must leave it in a usable state.
|
||
src = nlohmann::ordered_map<std::string, std::string> {};
|
||
src.emplace("after-move", "3");
|
||
REQUIRE(src.size() == 1);
|
||
CHECK(src.begin()->first == "after-move");
|
||
}
|
||
|
||
// Stand-in for a third-party library (e.g., Eigen as of 3.4, which added
|
||
// STL-compatible begin()/end() to its vector types), living in its own
|
||
// namespace with its own to_json overload for its vector type.
|
||
namespace issue_4320_eigen
|
||
{
|
||
// "array-compatible" from the library's point of view (it has begin()/end()),
|
||
// but for which this (fake) third-party namespace provides its own to_json.
|
||
struct vector3
|
||
{
|
||
double v[3]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-use-default-member-init,modernize-use-default-member-init)
|
||
vector3(double x, double y, double z) : v{x, y, z} {} // NOLINT(hicpp-member-init,cppcoreguidelines-pro-type-member-init)
|
||
double x() const
|
||
{
|
||
return v[0];
|
||
}
|
||
double y() const
|
||
{
|
||
return v[1];
|
||
}
|
||
double z() const
|
||
{
|
||
return v[2];
|
||
}
|
||
double* begin()
|
||
{
|
||
return v;
|
||
}
|
||
double* end()
|
||
{
|
||
return v + 3;
|
||
}
|
||
const double* begin() const
|
||
{
|
||
return v;
|
||
}
|
||
const double* end() const
|
||
{
|
||
return v + 3;
|
||
}
|
||
};
|
||
|
||
inline void to_json(json& j, const vector3& v) // NOLINT(misc-use-internal-linkage)
|
||
{
|
||
j = {{"x", v.x()}, {"y", v.y()}, {"z", v.z()}};
|
||
}
|
||
} // namespace issue_4320_eigen
|
||
|
||
// The user's own namespace, using the (fake) Eigen type as an implementation
|
||
// detail behind a payload type that has nothing to do with vectors/arrays.
|
||
namespace issue_4320
|
||
{
|
||
// Publicly derives from issue_4320_eigen::vector3 but does *not* define its
|
||
// own to_json - it is only ever used as a temporary to reach the base
|
||
// class's to_json via ADL.
|
||
struct vector3_wrapper : issue_4320_eigen::vector3
|
||
{
|
||
using issue_4320_eigen::vector3::vector3;
|
||
};
|
||
|
||
struct payload
|
||
{
|
||
double x, y, z;
|
||
};
|
||
|
||
inline vector3_wrapper to_eigen(const payload& p) // NOLINT(misc-use-internal-linkage)
|
||
{
|
||
return {p.x, p.y, p.z};
|
||
}
|
||
|
||
inline void to_json(json& j, const payload& p) // NOLINT(misc-use-internal-linkage)
|
||
{
|
||
// Unqualified call, passing a *derived* vector3_wrapper: relies on ADL
|
||
// finding issue_4320_eigen::to_json(json&, const vector3&) through the
|
||
// vector3 base class, via a derived-to-base conversion. Must NOT resolve
|
||
// to the library's own generic array-compatible to_json (an exact-match
|
||
// template for vector3_wrapper, since it also has begin()/end()), which
|
||
// would serialize this as [x, y, z] instead of {"x":x, "y":y, "z":z}.
|
||
to_json(j, to_eigen(p));
|
||
}
|
||
} // namespace issue_4320
|
||
|
||
TEST_CASE("issue #4320 - custom base class must not leak nlohmann::detail into ADL")
|
||
{
|
||
// Before the fix, basic_json unconditionally derived from a type living in
|
||
// nlohmann::detail (json_default_base), which made nlohmann::detail an
|
||
// associated namespace of every basic_json for ADL purposes. That leaked
|
||
// the library's internal generic-array to_json overload into unqualified
|
||
// to_json() calls made from user code, silently bypassing user-defined
|
||
// to_json overloads reached via a derived-to-base conversion.
|
||
const issue_4320::payload p{1.0, 2.0, 3.0};
|
||
|
||
json j;
|
||
to_json(j, p);
|
||
CHECK(j == json({{"x", 1.0}, {"y", 2.0}, {"z", 3.0}}));
|
||
}
|
||
|
||
TEST_CASE("issue #5338 - truncated CBOR tagged binary subtype is rejected")
|
||
{
|
||
const std::vector<std::vector<std::uint8_t>> truncated_tags =
|
||
{
|
||
{0xD8},
|
||
{0xD9, 0x00},
|
||
{0xDA, 0x00, 0x00, 0x00},
|
||
{0xDB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||
};
|
||
|
||
for (const auto& data : truncated_tags)
|
||
{
|
||
CAPTURE(data);
|
||
for (const auto tag_handler :
|
||
{
|
||
json::cbor_tag_handler_t::ignore, json::cbor_tag_handler_t::store
|
||
})
|
||
{
|
||
CAPTURE(tag_handler);
|
||
const auto result = json::from_cbor(data, true, false, tag_handler);
|
||
CHECK(result.is_discarded());
|
||
}
|
||
}
|
||
}
|
||
|
||
TEST_CASE("issue #5402 - update(merge_objects=true) overwrites a primitive with an object")
|
||
{
|
||
json t = {{"k", 1}};
|
||
t.update(json{{"k", {{"x", 2}}}}, true);
|
||
CHECK(t == json({{"k", {{"x", 2}}}}));
|
||
|
||
json mixed = {{"keep", {{"a", 1}}}, {"replace", 1}};
|
||
mixed.update(json{{"keep", {{"b", 2}}}, {"replace", {{"x", 2}}}}, true);
|
||
CHECK(mixed == json({{"keep", {{"a", 1}, {"b", 2}}}, {"replace", {{"x", 2}}}}));
|
||
}
|
||
|
||
|
||
TEST_CASE("regression test #5476 - array type without reserve()")
|
||
{
|
||
// the capacity reserved for definite-length arrays must not require the
|
||
// array type to have a reserve() member function
|
||
using deque_json = nlohmann::basic_json<std::map, std::deque>;
|
||
|
||
SECTION("std::deque")
|
||
{
|
||
const auto j = deque_json::parse(R"({"a":[1,[2,3]],"b":[]})");
|
||
CHECK(j.dump() == R"({"a":[1,[2,3]],"b":[]})");
|
||
|
||
// the binary formats pass a definite length to start_array()
|
||
CHECK(deque_json::from_cbor(deque_json::to_cbor(j)) == j);
|
||
CHECK(deque_json::from_msgpack(deque_json::to_msgpack(j)) == j);
|
||
|
||
// parse() instantiates the callback parser as well, which reserves too
|
||
const auto with_callback = deque_json::parse(R"([1,2,3])", [](int /*depth*/, deque_json::parse_event_t /*event*/, deque_json& /*parsed*/) noexcept
|
||
{
|
||
return true;
|
||
});
|
||
CHECK(with_callback == deque_json({1, 2, 3}));
|
||
}
|
||
|
||
SECTION("std::vector still reserves")
|
||
{
|
||
json array = json::array();
|
||
for (int i = 0; i < 100; ++i)
|
||
{
|
||
array.push_back(i);
|
||
}
|
||
|
||
const auto j = json::from_cbor(json::to_cbor(array));
|
||
CHECK(j == array);
|
||
CHECK(j.get_ref<const json::array_t&>().capacity() >= 100);
|
||
}
|
||
|
||
SECTION("the reservation stays capped")
|
||
{
|
||
// CBOR array announcing 2^32-1 elements, but truncated right after the
|
||
// header: the input must be rejected without reserving that capacity
|
||
const std::vector<std::uint8_t> truncated = {0x9A, 0xFF, 0xFF, 0xFF, 0xFF};
|
||
CHECK(json::from_cbor(truncated, true, false).is_discarded());
|
||
}
|
||
}
|
||
|
||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|