// __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ (supporting code) // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann // 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_ 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_ (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 #define JSON_TESTS_PRIVATE #include 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 #include #include #include #ifdef JSON_HAS_CPP_17 #include #include #endif #ifdef JSON_HAS_CPP_17 #if __has_include() #include #elif __has_include() #include #endif ///////////////////////////////////////////////////////////////////// // for #4804 ///////////////////////////////////////////////////////////////////// using json_4804 = nlohmann::basic_json, // BinaryType void // CustomBaseClass >; #endif #ifdef JSON_HAS_CPP_20 #if __has_include() #include #endif #endif ///////////////////////////////////////////////////////////////////// // for #4825 - explicitly instantiating basic_json must compile; this // forces instantiation of binary_writer::write_bjdata_ndarray, whose // static_cast 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 #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; ///////////////////////////////////////////////////////////////////// // 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 { static NonDefaultFromJsonStruct from_json(json const& /*unused*/) noexcept { return {}; } }; } // namespace nlohmann ///////////////////////////////////////////////////////////////////// // for #1805 ///////////////////////////////////////////////////////////////////// struct NotSerializableData { int mydata; float myfloat; }; 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; // 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>::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 expected { {"1", {"testa_1", "testb_1"}}, {"2", {"testa_2", "testb_2"}}, {"3", {"testa_3", "testb_3"}}, }; const auto data = j.get(); 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(-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(-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 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(i); } dump_test["1"] = s; 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::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(); 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 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 is json constructible only if T1 and T2 are json constructible") { static_assert(!std::is_constructible>::value, "unexpected result"); static_assert(!std::is_constructible>::value, "unexpected result"); static_assert(std::is_constructible>::value, "unexpected result"); } SECTION("issue #1825 - A tuple is json constructible only if all T in Args are json constructible") { static_assert(!std::is_constructible>::value, "unexpected result"); static_assert(!std::is_constructible>::value, "unexpected result"); static_assert(std::is_constructible>::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"}])"); } } DOCTEST_CLANG_SUPPRESS_WARNING_POP