mirror of
https://github.com/nlohmann/json.git
synced 2026-09-18 16:24:19 +00:00
json_sax_dom_callback_parser::key() unconditionally overwrote the object slot for a key with a `discarded` placeholder as soon as the key was accepted by the parser callback. For a duplicate key (legal JSON), this destroyed the pre-existing value from an earlier occurrence of the same key before the new value was even parsed. If the new value was then rejected by the callback, remove_discarded_value() erased the member entirely instead of leaving the original value in place, contradicting the documented behavior that a discarded value behaves as if it was never read. Add a small stash of (slot pointer, previous value) pairs so that when key() overwrites an existing member with the discarded placeholder, the previous value can be restored later if the corresponding value (scalar, object, or array) is rejected, instead of being erased. The stash entry is dropped without restoring once the new value is definitively accepted (in handle_value() for scalars, end_object()/end_array() for containers), so a duplicate key whose new value is accepted still keeps the last value as before. Non-duplicate keys are unaffected: rejecting their value still removes the member entirely, since there is nothing to restore. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
838 lines
28 KiB
C++
838 lines
28 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>
|
|
|
|
#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 <list>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
#include "test_utils.hpp"
|
|
|
|
#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>)
|
|
#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 #1021
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// for #1647
|
|
/////////////////////////////////////////////////////////////////////
|
|
namespace
|
|
{
|
|
struct NonDefaultFromJsonStruct
|
|
{};
|
|
|
|
inline bool operator==(NonDefaultFromJsonStruct const& /*unused*/, NonDefaultFromJsonStruct const& /*unused*/)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
enum class for_1647
|
|
{
|
|
one,
|
|
two
|
|
};
|
|
|
|
// NOLINTNEXTLINE(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): this is a false positive
|
|
NLOHMANN_JSON_SERIALIZE_ENUM(for_1647,
|
|
{
|
|
{for_1647::one, "one"},
|
|
{for_1647::two, "two"},
|
|
})
|
|
} // namespace
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// for #1299
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
struct Data
|
|
{
|
|
Data() = default;
|
|
Data(std::string a_, std::string b_)
|
|
: a(std::move(a_))
|
|
, b(std::move(b_))
|
|
{}
|
|
std::string a{}; // NOLINT(readability-redundant-member-init)
|
|
std::string b{}; // NOLINT(readability-redundant-member-init)
|
|
};
|
|
|
|
void from_json(const json& j, Data& data); // NOLINT(misc-use-internal-linkage)
|
|
void from_json(const json& j, Data& data)
|
|
{
|
|
j["a"].get_to(data.a);
|
|
j["b"].get_to(data.b);
|
|
}
|
|
|
|
bool operator==(Data const& lhs, Data const& rhs); // NOLINT(misc-use-internal-linkage)
|
|
bool operator==(Data const& lhs, Data const& rhs)
|
|
{
|
|
return lhs.a == rhs.a && lhs.b == rhs.b;
|
|
}
|
|
|
|
//bool operator!=(Data const& lhs, Data const& rhs)
|
|
//{
|
|
// return !(lhs == rhs);
|
|
//}
|
|
|
|
namespace nlohmann
|
|
{
|
|
template<>
|
|
struct adl_serializer<NonDefaultFromJsonStruct>
|
|
{
|
|
static NonDefaultFromJsonStruct from_json(json const& /*unused*/) noexcept
|
|
{
|
|
return {};
|
|
}
|
|
};
|
|
} // namespace nlohmann
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// for #1805
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
struct NotSerializableData
|
|
{
|
|
int mydata;
|
|
float myfloat;
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// for #2574
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
struct NonDefaultConstructible
|
|
{
|
|
explicit NonDefaultConstructible(int a)
|
|
: x(a)
|
|
{}
|
|
int x;
|
|
};
|
|
|
|
namespace nlohmann
|
|
{
|
|
template<>
|
|
struct adl_serializer<NonDefaultConstructible>
|
|
{
|
|
static NonDefaultConstructible from_json(json const& j)
|
|
{
|
|
return NonDefaultConstructible(j.get<int>());
|
|
}
|
|
};
|
|
} // namespace nlohmann
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// for #2824
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
class sax_no_exception : public nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type>
|
|
{
|
|
public:
|
|
explicit sax_no_exception(json& j)
|
|
: nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type>(j, false)
|
|
{}
|
|
|
|
static bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& ex)
|
|
{
|
|
error_string = new std::string(ex.what()); // NOLINT(cppcoreguidelines-owning-memory)
|
|
return false;
|
|
}
|
|
|
|
static std::string* error_string;
|
|
};
|
|
|
|
std::string* sax_no_exception::error_string = nullptr;
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// for #2982
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
template<class T>
|
|
class my_allocator : public std::allocator<T>
|
|
{
|
|
public:
|
|
using std::allocator<T>::allocator;
|
|
|
|
my_allocator() = default;
|
|
template<class U> my_allocator(const my_allocator<U>& /*unused*/) { }
|
|
|
|
template <class U>
|
|
struct rebind
|
|
{
|
|
using other = my_allocator<U>;
|
|
};
|
|
};
|
|
|
|
TEST_CASE("regression tests 2")
|
|
{
|
|
SECTION("issue #1001 - Fix memory leak during parser callback")
|
|
{
|
|
const auto* geojsonExample = R"(
|
|
{ "type": "FeatureCollection",
|
|
"features": [
|
|
{ "type": "Feature",
|
|
"geometry": {"type": "Point", "coordinates": [102.0, 0.5]},
|
|
"properties": {"prop0": "value0"}
|
|
},
|
|
{ "type": "Feature",
|
|
"geometry": {
|
|
"type": "LineString",
|
|
"coordinates": [
|
|
[102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]
|
|
]
|
|
},
|
|
"properties": {
|
|
"prop0": "value0",
|
|
"prop1": 0.0
|
|
}
|
|
},
|
|
{ "type": "Feature",
|
|
"geometry": {
|
|
"type": "Polygon",
|
|
"coordinates": [
|
|
[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],
|
|
[100.0, 1.0], [100.0, 0.0] ]
|
|
]
|
|
},
|
|
"properties": {
|
|
"prop0": "value0",
|
|
"prop1": {"this": "that"}
|
|
}
|
|
}
|
|
]
|
|
})";
|
|
|
|
const json::parser_callback_t cb = [&](int /*level*/, json::parse_event_t event, json & parsed) noexcept
|
|
{
|
|
// skip uninteresting events
|
|
if (event == json::parse_event_t::value && !parsed.is_primitive())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
switch (event)
|
|
{
|
|
case json::parse_event_t::key:
|
|
{
|
|
return true;
|
|
}
|
|
case json::parse_event_t::value:
|
|
{
|
|
return false;
|
|
}
|
|
case json::parse_event_t::object_start:
|
|
{
|
|
return true;
|
|
}
|
|
case json::parse_event_t::object_end:
|
|
{
|
|
return false;
|
|
}
|
|
case json::parse_event_t::array_start:
|
|
{
|
|
return true;
|
|
}
|
|
case json::parse_event_t::array_end:
|
|
{
|
|
return false;
|
|
}
|
|
|
|
default:
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
};
|
|
|
|
auto j = json::parse(geojsonExample, cb, true);
|
|
CHECK(j == json());
|
|
}
|
|
|
|
SECTION("issue #1021 - to/from_msgpack only works with standard typization")
|
|
{
|
|
float_json j = 1000.0;
|
|
CHECK(float_json::from_cbor(float_json::to_cbor(j)) == j);
|
|
CHECK(float_json::from_msgpack(float_json::to_msgpack(j)) == j);
|
|
CHECK(float_json::from_ubjson(float_json::to_ubjson(j)) == j);
|
|
|
|
float_json j2 = {1000.0, 2000.0, 3000.0};
|
|
CHECK(float_json::from_ubjson(float_json::to_ubjson(j2, true, true)) == j2);
|
|
}
|
|
|
|
SECTION("issue #1045 - Using STL algorithms with JSON containers with expected results?")
|
|
{
|
|
json diffs = nlohmann::json::array();
|
|
json m1{{"key1", 42}};
|
|
json m2{{"key2", 42}};
|
|
auto p1 = m1.items();
|
|
auto p2 = m2.items();
|
|
|
|
using it_type = decltype(p1.begin());
|
|
|
|
std::set_difference(
|
|
p1.begin(),
|
|
p1.end(),
|
|
p2.begin(),
|
|
p2.end(),
|
|
std::inserter(diffs, diffs.end()),
|
|
[&](const it_type & e1, const it_type & e2) -> bool
|
|
{
|
|
using comper_pair = std::pair<std::string, decltype(e1.value())>; // Trying to avoid unneeded copy
|
|
return comper_pair(e1.key(), e1.value()) < comper_pair(e2.key(), e2.value()); // Using pair comper
|
|
});
|
|
|
|
CHECK(diffs.size() == 1); // Note the change here, was 2
|
|
}
|
|
|
|
#ifdef JSON_HAS_CPP_17
|
|
SECTION("issue #1292 - Serializing std::variant causes stack overflow")
|
|
{
|
|
static_assert(!std::is_constructible<json, std::variant<int, float>>::value, "unexpected value");
|
|
}
|
|
#endif
|
|
|
|
SECTION("issue #1299 - compile error in from_json converting to container "
|
|
"with std::pair")
|
|
{
|
|
const json j =
|
|
{
|
|
{"1", {{"a", "testa_1"}, {"b", "testb_1"}}},
|
|
{"2", {{"a", "testa_2"}, {"b", "testb_2"}}},
|
|
{"3", {{"a", "testa_3"}, {"b", "testb_3"}}},
|
|
};
|
|
|
|
const std::map<std::string, Data> expected
|
|
{
|
|
{"1", {"testa_1", "testb_1"}},
|
|
{"2", {"testa_2", "testb_2"}},
|
|
{"3", {"testa_3", "testb_3"}},
|
|
};
|
|
const auto data = j.get<decltype(expected)>();
|
|
CHECK(expected == data);
|
|
}
|
|
|
|
SECTION("issue #1445 - buffer overflow in dumping invalid utf-8 strings")
|
|
{
|
|
SECTION("a bunch of -1, ensure_ascii=true")
|
|
{
|
|
const auto length = 300;
|
|
|
|
json dump_test;
|
|
dump_test["1"] = std::string(length, static_cast<std::string::value_type>(-1));
|
|
|
|
std::string expected = R"({"1":")";
|
|
for (int i = 0; i < length; ++i)
|
|
{
|
|
expected += "\\ufffd";
|
|
}
|
|
expected += "\"}";
|
|
|
|
auto s = dump_test.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace);
|
|
CHECK(s == expected);
|
|
}
|
|
SECTION("a bunch of -2, ensure_ascii=false")
|
|
{
|
|
const auto length = 500;
|
|
|
|
json dump_test;
|
|
dump_test["1"] = std::string(length, static_cast<std::string::value_type>(-2));
|
|
|
|
std::string expected = R"({"1":")";
|
|
for (int i = 0; i < length; ++i)
|
|
{
|
|
expected += "\xEF\xBF\xBD";
|
|
}
|
|
expected += "\"}";
|
|
|
|
auto s = dump_test.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace);
|
|
CHECK(s == expected);
|
|
}
|
|
SECTION("test case in issue #1445")
|
|
{
|
|
nlohmann::json dump_test;
|
|
const std::array<int, 108> data =
|
|
{
|
|
{109, 108, 103, 125, -122, -53, 115, 18, 3, 0, 102, 19, 1, 15, -110, 13, -3, -1, -81, 32, 2, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -80, 2, 0, 0, 96, -118, 46, -116, 46, 109, -84, -87, 108, 14, 109, -24, -83, 13, -18, -51, -83, -52, -115, 14, 6, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 3, 0, 0, 0, 35, -74, -73, 55, 57, -128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, -96, -54, -28, -26}
|
|
};
|
|
std::string s;
|
|
for (const int i : data)
|
|
{
|
|
s += static_cast<char>(i);
|
|
}
|
|
dump_test["1"] = s;
|
|
// dump() is nodiscard; this only checks that dumping does not throw/crash
|
|
utils::ignore_return_value(dump_test.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace));
|
|
}
|
|
}
|
|
|
|
SECTION("issue #1447 - Integer Overflow (OSS-Fuzz 12506)")
|
|
{
|
|
const json j = json::parse("[-9223372036854775808]");
|
|
CHECK(j.dump() == "[-9223372036854775808]");
|
|
}
|
|
|
|
SECTION("issue #1708 - minimum value of int64_t can be outputted")
|
|
{
|
|
constexpr auto smallest = (std::numeric_limits<int64_t>::min)();
|
|
const json j = smallest;
|
|
CHECK(j.dump() == std::to_string(smallest));
|
|
}
|
|
|
|
SECTION("issue #1727 - Contains with non-const lvalue json_pointer picks the wrong overload")
|
|
{
|
|
const json j = {{"root", {{"settings", {{"logging", true}}}}}};
|
|
|
|
auto jptr1 = "/root/settings/logging"_json_pointer;
|
|
auto jptr2 = json::json_pointer{"/root/settings/logging"};
|
|
|
|
CHECK(j.contains(jptr1));
|
|
CHECK(j.contains(jptr2));
|
|
}
|
|
|
|
SECTION("issue #1647 - compile error when deserializing enum if both non-default from_json and non-member operator== exists for other type")
|
|
{
|
|
// does not compile on ICPC when targeting C++20
|
|
#if !(defined(__INTEL_COMPILER) && __cplusplus >= 202000)
|
|
{
|
|
const json j;
|
|
const NonDefaultFromJsonStruct x(j);
|
|
NonDefaultFromJsonStruct y;
|
|
CHECK(x == y);
|
|
}
|
|
#endif
|
|
|
|
auto val = nlohmann::json("one").get<for_1647>();
|
|
CHECK(val == for_1647::one);
|
|
const json j = val;
|
|
}
|
|
|
|
SECTION("issue #1715 - json::from_cbor does not respect allow_exceptions = false when input is string literal")
|
|
{
|
|
SECTION("string literal")
|
|
{
|
|
const json cbor = json::from_cbor("B", true, false);
|
|
CHECK(cbor.is_discarded());
|
|
}
|
|
|
|
SECTION("string array")
|
|
{
|
|
const std::array<char, 2> input = {{'B', 0x00}};
|
|
const json cbor = json::from_cbor(input, true, false);
|
|
CHECK(cbor.is_discarded());
|
|
}
|
|
|
|
SECTION("std::string")
|
|
{
|
|
const json cbor = json::from_cbor(std::string("B"), true, false);
|
|
CHECK(cbor.is_discarded());
|
|
}
|
|
}
|
|
|
|
SECTION("issue #1805 - A pair<T1, T2> is json constructible only if T1 and T2 are json constructible")
|
|
{
|
|
static_assert(!std::is_constructible<json, std::pair<std::string, NotSerializableData>>::value, "unexpected result");
|
|
static_assert(!std::is_constructible<json, std::pair<NotSerializableData, std::string>>::value, "unexpected result");
|
|
static_assert(std::is_constructible<json, std::pair<int, std::string>>::value, "unexpected result");
|
|
}
|
|
SECTION("issue #1825 - A tuple<Args..> is json constructible only if all T in Args are json constructible")
|
|
{
|
|
static_assert(!std::is_constructible<json, std::tuple<std::string, NotSerializableData>>::value, "unexpected result");
|
|
static_assert(!std::is_constructible<json, std::tuple<NotSerializableData, std::string>>::value, "unexpected result");
|
|
static_assert(std::is_constructible<json, std::tuple<int, std::string>>::value, "unexpected result");
|
|
}
|
|
|
|
SECTION("issue #1983 - JSON patch diff for op=add formation is not as per standard (RFC 6902)")
|
|
{
|
|
const auto source = R"({ "foo": [ "1", "2" ] })"_json;
|
|
const auto target = R"({"foo": [ "1", "2", "3" ]})"_json;
|
|
const auto result = json::diff(source, target);
|
|
CHECK(result.dump() == R"([{"op":"add","path":"/foo/-","value":"3"}])");
|
|
}
|
|
|
|
SECTION("issue #2067 - cannot serialize binary data to text JSON")
|
|
{
|
|
const std::array<unsigned char, 23> data = {{0x81, 0xA4, 0x64, 0x61, 0x74, 0x61, 0xC4, 0x0F, 0x33, 0x30, 0x30, 0x32, 0x33, 0x34, 0x30, 0x31, 0x30, 0x37, 0x30, 0x35, 0x30, 0x31, 0x30}};
|
|
const json j = json::from_msgpack(data.data(), data.size());
|
|
// dump() is nodiscard; this only checks that dumping does not throw
|
|
CHECK_NOTHROW(
|
|
utils::ignore_return_value(
|
|
j.dump(4, // Indent
|
|
' ', // Indent char
|
|
false, // Ensure ascii
|
|
json::error_handler_t::strict // Error
|
|
)));
|
|
}
|
|
|
|
SECTION("PR #2181 - regression bug with lvalue")
|
|
{
|
|
// see https://github.com/nlohmann/json/pull/2181#issuecomment-653326060
|
|
const json j{{"x", "test"}};
|
|
const std::string defval = "default value";
|
|
auto val = j.value("x", defval); // NOLINT(bugprone-unused-local-non-trivial-variable)
|
|
auto val2 = j.value("y", defval); // NOLINT(bugprone-unused-local-non-trivial-variable)
|
|
}
|
|
|
|
SECTION("issue #2293 - eof doesn't cause parsing to stop")
|
|
{
|
|
const std::vector<uint8_t> data =
|
|
{
|
|
0x7B,
|
|
0x6F,
|
|
0x62,
|
|
0x6A,
|
|
0x65,
|
|
0x63,
|
|
0x74,
|
|
0x20,
|
|
0x4F,
|
|
0x42
|
|
};
|
|
const json result = json::from_cbor(data, true, false);
|
|
CHECK(result.is_discarded());
|
|
}
|
|
|
|
SECTION("issue #2315 - json.update and vector<pair>does not work with ordered_json")
|
|
{
|
|
nlohmann::ordered_json jsonAnimals = {{"animal", "dog"}};
|
|
const nlohmann::ordered_json jsonCat = {{"animal", "cat"}};
|
|
jsonAnimals.update(jsonCat);
|
|
CHECK(jsonAnimals["animal"] == "cat");
|
|
|
|
auto jsonAnimals_parsed = nlohmann::ordered_json::parse(jsonAnimals.dump());
|
|
CHECK(jsonAnimals == jsonAnimals_parsed);
|
|
|
|
const std::vector<std::pair<std::string, int64_t>> intData = {std::make_pair("aaaa", 11),
|
|
std::make_pair("bbb", 222)
|
|
};
|
|
nlohmann::ordered_json jsonObj;
|
|
for (const auto& data : intData)
|
|
{
|
|
jsonObj[data.first] = data.second;
|
|
}
|
|
CHECK(jsonObj["aaaa"] == 11);
|
|
CHECK(jsonObj["bbb"] == 222);
|
|
}
|
|
|
|
SECTION("issue #2330 - ignore_comment=true fails on multiple consecutive lines starting with comments")
|
|
{
|
|
const std::string ss = "//\n//\n{\n}\n";
|
|
const json j = json::parse(ss, nullptr, true, true);
|
|
CHECK(j.dump() == "{}");
|
|
}
|
|
|
|
#ifdef JSON_HAS_CPP_20
|
|
#ifndef _LIBCPP_VERSION // see https://github.com/nlohmann/json/issues/4490
|
|
// classic Intel ICC reports <span> as includable but cannot actually compile
|
|
// std::span/std::as_bytes usage below
|
|
#if __has_include(<span>) && !defined(__ICC) && !defined(__INTEL_COMPILER)
|
|
SECTION("issue #2546 - parsing containers of std::byte")
|
|
{
|
|
const char DATA[] = R"("Hello, world!")"; // NOLINT(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
|
const auto s = std::as_bytes(std::span(DATA));
|
|
const json j = json::parse(s);
|
|
CHECK(j.dump() == "\"Hello, world!\"");
|
|
}
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
SECTION("issue #2574 - Deserialization to std::array, std::pair, and std::tuple with non-default constructable types fails")
|
|
{
|
|
SECTION("std::array")
|
|
{
|
|
{
|
|
const json j = {7, 4};
|
|
auto arr = j.get<std::array<NonDefaultConstructible, 2>>();
|
|
CHECK(arr[0].x == 7);
|
|
CHECK(arr[1].x == 4);
|
|
}
|
|
|
|
{
|
|
const json j = 7;
|
|
CHECK_THROWS_AS((j.get<std::array<NonDefaultConstructible, 1>>()), json::type_error);
|
|
}
|
|
}
|
|
|
|
SECTION("std::pair")
|
|
{
|
|
{
|
|
const json j = {3, 8};
|
|
auto p = j.get<std::pair<NonDefaultConstructible, NonDefaultConstructible>>();
|
|
CHECK(p.first.x == 3);
|
|
CHECK(p.second.x == 8);
|
|
}
|
|
|
|
{
|
|
const json j = {4, 1};
|
|
auto p = j.get<std::pair<int, NonDefaultConstructible>>();
|
|
CHECK(p.first == 4);
|
|
CHECK(p.second.x == 1);
|
|
}
|
|
|
|
{
|
|
const json j = {6, 7};
|
|
auto p = j.get<std::pair<NonDefaultConstructible, int>>();
|
|
CHECK(p.first.x == 6);
|
|
CHECK(p.second == 7);
|
|
}
|
|
|
|
{
|
|
const json j = 7;
|
|
CHECK_THROWS_AS((j.get<std::pair<NonDefaultConstructible, int>>()), json::type_error);
|
|
}
|
|
}
|
|
|
|
SECTION("std::tuple")
|
|
{
|
|
{
|
|
const json j = {9};
|
|
auto t = j.get<std::tuple<NonDefaultConstructible>>();
|
|
CHECK(std::get<0>(t).x == 9);
|
|
}
|
|
|
|
{
|
|
const json j = {9, 8, 7};
|
|
auto t = j.get<std::tuple<NonDefaultConstructible, int, NonDefaultConstructible>>();
|
|
CHECK(std::get<0>(t).x == 9);
|
|
CHECK(std::get<1>(t) == 8);
|
|
CHECK(std::get<2>(t).x == 7);
|
|
}
|
|
|
|
{
|
|
const json j = 7;
|
|
CHECK_THROWS_AS((j.get<std::tuple<NonDefaultConstructible>>()), json::type_error);
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("issue #4530 - Serialization of empty tuple")
|
|
{
|
|
const auto source_tuple = std::tuple<>();
|
|
const nlohmann::json j = source_tuple;
|
|
|
|
CHECK(j.get<decltype(source_tuple)>() == source_tuple);
|
|
CHECK("[]" == j.dump());
|
|
}
|
|
|
|
SECTION("issue #2865 - ASAN detects memory leaks")
|
|
{
|
|
// the code below is expected to not leak memory
|
|
{
|
|
nlohmann::json o;
|
|
const std::string s = "bar";
|
|
|
|
nlohmann::to_json(o["foo"], s);
|
|
|
|
nlohmann::json p = o;
|
|
|
|
// call to_json with a non-null JSON value
|
|
nlohmann::to_json(p["foo"], s);
|
|
}
|
|
|
|
{
|
|
nlohmann::json o;
|
|
const std::string s = "bar";
|
|
|
|
nlohmann::to_json(o["foo"], s);
|
|
|
|
// call to_json with a non-null JSON value
|
|
nlohmann::to_json(o["foo"], s);
|
|
}
|
|
}
|
|
|
|
SECTION("issue #2824 - encoding of json::exception::what()")
|
|
{
|
|
json j;
|
|
sax_no_exception sax(j);
|
|
|
|
CHECK(!json::sax_parse("xyz", &sax));
|
|
CHECK(*sax_no_exception::error_string == "[json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - invalid literal; last read: 'x'");
|
|
delete sax_no_exception::error_string; // NOLINT(cppcoreguidelines-owning-memory)
|
|
}
|
|
|
|
SECTION("issue #2825 - Properly constrain the basic_json conversion operator")
|
|
{
|
|
static_assert(std::is_copy_assignable<nlohmann::ordered_json>::value, "ordered_json must be copy assignable");
|
|
}
|
|
|
|
SECTION("issue #2958 - Inserting in unordered json using a pointer retains the leading slash")
|
|
{
|
|
const std::string p = "/root";
|
|
|
|
json test1;
|
|
test1[json::json_pointer(p)] = json::object();
|
|
CHECK(test1.dump() == "{\"root\":{}}");
|
|
|
|
ordered_json test2;
|
|
test2[ordered_json::json_pointer(p)] = json::object();
|
|
CHECK(test2.dump() == "{\"root\":{}}");
|
|
|
|
// json::json_pointer and ordered_json::json_pointer are the same type; behave as above
|
|
ordered_json test3;
|
|
test3[json::json_pointer(p)] = json::object();
|
|
CHECK(std::is_same<json::json_pointer::string_t, ordered_json::json_pointer::string_t>::value);
|
|
CHECK(test3.dump() == "{\"root\":{}}");
|
|
}
|
|
|
|
SECTION("issue #2982 - to_{binary format} does not provide a mechanism for specifying a custom allocator for the returned type")
|
|
{
|
|
std::vector<std::uint8_t, my_allocator<std::uint8_t>> my_vector;
|
|
const json j = {1, 2, 3, 4};
|
|
json::to_cbor(j, my_vector);
|
|
json k = json::from_cbor(my_vector);
|
|
CHECK(j == k);
|
|
}
|
|
|
|
}
|
|
|
|
TEST_CASE("regression test - parser callback must not lose a duplicate key's prior value")
|
|
{
|
|
// a callback that rejects only the scalar value 2
|
|
const json::parser_callback_t drop_value_2 = [](int /*depth*/, json::parse_event_t ev, json & v)
|
|
{
|
|
return !(ev == json::parse_event_t::value && v == 2);
|
|
};
|
|
|
|
SECTION("duplicate key, second (scalar) value rejected - prior value is restored")
|
|
{
|
|
const json j = json::parse(R"({"a":1,"a":2})", drop_value_2);
|
|
CHECK(j.dump() == "{\"a\":1}");
|
|
}
|
|
|
|
SECTION("duplicate key, second value is an object rejected at object_end - prior value is restored")
|
|
{
|
|
const json j = json::parse(R"({"a":1,"a":{"x":2}})",
|
|
[](int depth, json::parse_event_t ev, json& /*parsed*/)
|
|
{
|
|
return !(ev == json::parse_event_t::object_end && depth == 1);
|
|
});
|
|
CHECK(j.dump() == "{\"a\":1}");
|
|
}
|
|
|
|
SECTION("duplicate key, second value is an array rejected at array_end - prior value is restored")
|
|
{
|
|
const json j = json::parse(R"({"a":1,"a":[9,9]})",
|
|
[](int depth, json::parse_event_t ev, json& /*parsed*/)
|
|
{
|
|
return !(ev == json::parse_event_t::array_end && depth == 1);
|
|
});
|
|
CHECK(j.dump() == "{\"a\":1}");
|
|
}
|
|
|
|
SECTION("duplicate key, second value accepted (scalar) - last value wins")
|
|
{
|
|
const json j = json::parse(R"({"a":1,"a":2})", [](int, json::parse_event_t, json&) noexcept
|
|
{
|
|
return true;
|
|
});
|
|
CHECK(j.dump() == "{\"a\":2}");
|
|
}
|
|
|
|
SECTION("duplicate key, second value accepted (object) - last value wins")
|
|
{
|
|
const json j = json::parse(R"({"a":1,"a":{"x":2}})", [](int, json::parse_event_t, json&) noexcept
|
|
{
|
|
return true;
|
|
});
|
|
CHECK(j.dump() == "{\"a\":{\"x\":2}}");
|
|
}
|
|
|
|
SECTION("brand new (non-duplicate) key, value rejected - member is fully absent")
|
|
{
|
|
const json j = json::parse(R"({"a":1,"b":2})", drop_value_2);
|
|
CHECK(j.dump() == "{\"a\":1}");
|
|
}
|
|
|
|
SECTION("duplicate key nested two levels deep")
|
|
{
|
|
const json j = json::parse(R"({"outer":{"a":1,"a":2}})", drop_value_2);
|
|
CHECK(j.dump() == "{\"outer\":{\"a\":1}}");
|
|
}
|
|
|
|
SECTION("three occurrences of the same key - middle rejected, last accepted")
|
|
{
|
|
const json j = json::parse(R"({"k":1,"k":2,"k":3})", drop_value_2);
|
|
CHECK(j.dump() == "{\"k\":3}");
|
|
}
|
|
}
|
|
|
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|