From e8239afff1cd5de7c4fa60fc1ddfe81bb31bd61c Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 14:49:52 +0200 Subject: [PATCH] Split unit-conversions.cpp so MinGW can link it clang 18 with the MinGW linker failed to link test-conversions_cpp17 ("relocation truncated to fit: IMAGE_REL_AMD64_REL32"). As windows.yml recommends, keep the objects small by splitting the test file. Signed-off-by: Niels Lohmann --- ...-conversions.cpp => unit-conversions1.cpp} | 805 ---------------- tests/src/unit-conversions2.cpp | 868 ++++++++++++++++++ 2 files changed, 868 insertions(+), 805 deletions(-) rename tests/src/{unit-conversions.cpp => unit-conversions1.cpp} (52%) create mode 100644 tests/src/unit-conversions2.cpp diff --git a/tests/src/unit-conversions.cpp b/tests/src/unit-conversions1.cpp similarity index 52% rename from tests/src/unit-conversions.cpp rename to tests/src/unit-conversions1.cpp index 5f83a9253..3d39e10c9 100644 --- a/tests/src/unit-conversions.cpp +++ b/tests/src/unit-conversions1.cpp @@ -15,11 +15,6 @@ #include "doctest_compatibility.h" -// skip tests if JSON_DisableEnumSerialization=ON (#4384) -#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1) - #define SKIP_TESTS_FOR_ENUM_SERIALIZATION -#endif - #define JSON_TESTS_PRIVATE #include using nlohmann::json; @@ -1259,808 +1254,8 @@ TEST_CASE("value conversion") } } #endif - - SECTION("get a binary value (explicit)") - { - json::binary_t const n_reference{{1, 2, 3}}; - json j(n_reference); - - SECTION("binary_t") - { - json::binary_t const b = j.get(); - CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); - } - - SECTION("get_binary()") - { - SECTION("non-const") - { - auto& b = j.get_binary(); - CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); - } - - SECTION("non-const") - { - const json j_const = j; // NOLINT(performance-unnecessary-copy-initialization) - const auto& b = j_const.get_binary(); - CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); - } - } - - SECTION("exception in case of a non-string type") - { - json j_null(json::value_t::null); - json j_object(json::value_t::object); - json j_array(json::value_t::array); - json j_string(json::value_t::string); - json j_boolean(json::value_t::boolean); - const json j_null_const(json::value_t::null); - const json j_object_const(json::value_t::object); - const json j_array_const(json::value_t::array); - const json j_string_const(json::value_t::string); - const json j_boolean_const(json::value_t::boolean); - - CHECK_THROWS_WITH_AS(j_null.get(), - "[json.exception.type_error.302] type must be binary, but is null", - json::type_error&); - CHECK_THROWS_WITH_AS(j_object.get(), - "[json.exception.type_error.302] type must be binary, but is object", - json::type_error&); - CHECK_THROWS_WITH_AS(j_array.get(), - "[json.exception.type_error.302] type must be binary, but is array", - json::type_error&); - CHECK_THROWS_WITH_AS(j_string.get(), - "[json.exception.type_error.302] type must be binary, but is string", - json::type_error&); - CHECK_THROWS_WITH_AS(j_boolean.get(), - "[json.exception.type_error.302] type must be binary, but is boolean", - json::type_error&); - - CHECK_THROWS_WITH_AS(j_null_const.get(), - "[json.exception.type_error.302] type must be binary, but is null", - json::type_error&); - CHECK_THROWS_WITH_AS(j_object_const.get(), - "[json.exception.type_error.302] type must be binary, but is object", - json::type_error&); - CHECK_THROWS_WITH_AS(j_array_const.get(), - "[json.exception.type_error.302] type must be binary, but is array", - json::type_error&); - CHECK_THROWS_WITH_AS(j_string_const.get(), - "[json.exception.type_error.302] type must be binary, but is string", - json::type_error&); - CHECK_THROWS_WITH_AS(j_boolean_const.get(), - "[json.exception.type_error.302] type must be binary, but is boolean", - json::type_error&); - - CHECK_THROWS_WITH_AS(j_null.get_binary(), - "[json.exception.type_error.302] type must be binary, but is null", - json::type_error&); - CHECK_THROWS_WITH_AS(j_object.get_binary(), - "[json.exception.type_error.302] type must be binary, but is object", - json::type_error&); - CHECK_THROWS_WITH_AS(j_array.get_binary(), - "[json.exception.type_error.302] type must be binary, but is array", - json::type_error&); - CHECK_THROWS_WITH_AS(j_string.get_binary(), - "[json.exception.type_error.302] type must be binary, but is string", - json::type_error&); - CHECK_THROWS_WITH_AS(j_boolean.get_binary(), - "[json.exception.type_error.302] type must be binary, but is boolean", - json::type_error&); - - CHECK_THROWS_WITH_AS(j_null_const.get_binary(), - "[json.exception.type_error.302] type must be binary, but is null", - json::type_error&); - CHECK_THROWS_WITH_AS(j_object_const.get_binary(), - "[json.exception.type_error.302] type must be binary, but is object", - json::type_error&); - CHECK_THROWS_WITH_AS(j_array_const.get_binary(), - "[json.exception.type_error.302] type must be binary, but is array", - json::type_error&); - CHECK_THROWS_WITH_AS(j_string_const.get_binary(), - "[json.exception.type_error.302] type must be binary, but is string", - json::type_error&); - CHECK_THROWS_WITH_AS(j_boolean_const.get_binary(), - "[json.exception.type_error.302] type must be binary, but is boolean", - json::type_error&); - } - } - -#if JSON_USE_IMPLICIT_CONVERSIONS - SECTION("get a binary value (implicit)") - { - json::binary_t const n_reference{{1, 2, 3}}; - json const j(n_reference); - - SECTION("binary_t") - { - json::binary_t const b = j; - CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); - } - } -#endif - -#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION - SECTION("get an enum") - { - enum c_enum { value_1, value_2 }; // NOLINT(cppcoreguidelines-use-enum-class) - enum class cpp_enum { value_1, value_2 }; - - CHECK(json(value_1).get() == value_1); - CHECK(json(cpp_enum::value_1).get() == cpp_enum::value_1); - } - - SECTION("get an enum with underlying type bool (#5671)") - { - enum class bool_enum : bool { off, on }; - - CHECK(json(bool_enum::off).get() == bool_enum::off); - CHECK(json(bool_enum::on).get() == bool_enum::on); - } -#endif - - SECTION("more involved conversions") - { - SECTION("object-like STL containers") - { - json const j1 = {{"one", 1}, {"two", 2}, {"three", 3}}; - json const j2 = {{"one", 1u}, {"two", 2u}, {"three", 3u}}; - json const j3 = {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}; - json const j4 = {{"one", true}, {"two", false}, {"three", true}}; - json const j5 = {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}; - - SECTION("std::map") - { - CHECK(j1.get>() == (std::map {{"one", 1}, {"two", 2}, {"three", 3}})); - CHECK(j2.get>() == (std::map {{"one", 1u}, {"two", 2u}, {"three", 3u}})); - CHECK(j3.get>() == (std::map {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); - CHECK(j4.get>() == (std::map {{"one", true}, {"two", false}, {"three", true}})); - CHECK(j5.get>() == (std::map {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); - } - - SECTION("std::unordered_map") - { - CHECK(j1.get>() == (std::unordered_map {{"one", 1}, {"two", 2}, {"three", 3}})); - CHECK(j2.get>() == (std::unordered_map {{"one", 1u}, {"two", 2u}, {"three", 3u}})); - CHECK(j3.get>() == (std::unordered_map {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); - CHECK(j4.get>() == (std::unordered_map {{"one", true}, {"two", false}, {"three", true}})); - const auto m5 = j5.get>(); - CHECK(m5 == (std::unordered_map {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); - CHECK(m5.at("one") == "eins"); - } - - SECTION("reserve is called on containers that support it (#5406)") - { - // build a larger object so that a missing/incorrect reserve() - // call would be more likely to corrupt or drop elements - json j_large; - for (int i = 0; i < 100; ++i) - { - j_large[std::to_string(i)] = i; - } - - SECTION("std::unordered_map (supports reserve)") - { - const auto m = j_large.get>(); - CHECK(m.size() == 100); - for (int i = 0; i < 100; ++i) - { - CHECK(m.at(std::to_string(i)) == i); - } - } - - SECTION("std::map (no reserve, fallback path)") - { - const auto m = j_large.get>(); - CHECK(m.size() == 100); - for (int i = 0; i < 100; ++i) - { - CHECK(m.at(std::to_string(i)) == i); - } - } - } - - SECTION("std::multimap") - { - CHECK(j1.get>() == (std::multimap {{"one", 1}, {"two", 2}, {"three", 3}})); - CHECK(j2.get>() == (std::multimap {{"one", 1u}, {"two", 2u}, {"three", 3u}})); - CHECK(j3.get>() == (std::multimap {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); - CHECK(j4.get>() == (std::multimap {{"one", true}, {"two", false}, {"three", true}})); - const auto m5 = j5.get>(); - CHECK(m5 == (std::multimap {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); - CHECK(m5.find("one")->second == "eins"); - } - - SECTION("std::unordered_multimap") - { - CHECK(j1.get>() == (std::unordered_multimap {{"one", 1}, {"two", 2}, {"three", 3}})); - CHECK(j2.get>() == (std::unordered_multimap {{"one", 1u}, {"two", 2u}, {"three", 3u}})); - CHECK(j3.get>() == (std::unordered_multimap {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); - CHECK(j4.get>() == (std::unordered_multimap {{"one", true}, {"two", false}, {"three", true}})); - const auto m5 = j5.get>(); - CHECK(m5 == (std::unordered_multimap {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); - CHECK(m5.find("one")->second == "eins"); - } - - SECTION("exception in case of a non-object type") - { - CHECK_THROWS_WITH_AS( - (json().get>()), - "[json.exception.type_error.302] type must be object, but is null", json::type_error&); - } - } - - SECTION("array-like STL containers") - { - json const j1 = {1, 2, 3, 4}; - json const j2 = {1u, 2u, 3u, 4u}; - json const j3 = {1.2, 2.3, 3.4, 4.5}; - json const j4 = {true, false, true}; - json const j5 = {"one", "two", "three"}; - - SECTION("std::list") - { - CHECK(j1.get>() == (std::list {1, 2, 3, 4})); - CHECK(j2.get>() == (std::list {1u, 2u, 3u, 4u})); - CHECK(j3.get>() == (std::list {1.2, 2.3, 3.4, 4.5})); - CHECK(j4.get>() == (std::list {true, false, true})); - CHECK(j5.get>() == (std::list {"one", "two", "three"})); - } - - SECTION("std::forward_list") - { - CHECK(j1.get>() == (std::forward_list {1, 2, 3, 4})); - CHECK(j2.get>() == (std::forward_list {1u, 2u, 3u, 4u})); - CHECK(j3.get>() == (std::forward_list {1.2, 2.3, 3.4, 4.5})); - CHECK(j4.get>() == (std::forward_list {true, false, true})); - CHECK(j5.get>() == (std::forward_list {"one", "two", "three"})); - } - - SECTION("std::array") - { - CHECK(j1.get>() == (std::array {{1, 2, 3, 4}})); - // only the first 3 elements of j2 are converted, since the target array is smaller - CHECK(j2.get>() == (std::array {{1u, 2u, 3u}})); - CHECK(j3.get>() == (std::array {{1.2, 2.3, 3.4, 4.5}})); - CHECK(j4.get>() == (std::array {{true, false, true}})); - CHECK(j5.get>() == (std::array {{"one", "two", "three"}})); - - SECTION("std::array is larger than JSON") - { - std::array arr6 = {{1, 2, 3, 4, 5, 6}}; - CHECK_THROWS_WITH_AS(j1.get_to(arr6), "[json.exception.out_of_range.401] " - "array index 4 is out of range", json::out_of_range&); - } - - SECTION("std::array is smaller than JSON") - { - std::array arr2 = {{8, 9}}; - j1.get_to(arr2); - CHECK(arr2[0] == 1); - CHECK(arr2[1] == 2); - } - } - - SECTION("std::valarray") - { - // valarray has no operator== that returns bool, so compare via a vector copy - const auto v1 = j1.get>(); - CHECK((std::vector(std::begin(v1), std::end(v1)) == std::vector {1, 2, 3, 4})); - const auto v2 = j2.get>(); - CHECK((std::vector(std::begin(v2), std::end(v2)) == std::vector {1u, 2u, 3u, 4u})); - const auto v3 = j3.get>(); - CHECK((std::vector(std::begin(v3), std::end(v3)) == std::vector {1.2, 2.3, 3.4, 4.5})); - const auto v4 = j4.get>(); - CHECK((std::vector(std::begin(v4), std::end(v4)) == std::vector {true, false, true})); - const auto v5 = j5.get>(); - CHECK((std::vector(std::begin(v5), std::end(v5)) == std::vector {"one", "two", "three"})); - } - - SECTION("std::vector") - { - CHECK(j1.get>() == (std::vector {1, 2, 3, 4})); - CHECK(j2.get>() == (std::vector {1u, 2u, 3u, 4u})); - CHECK(j3.get>() == (std::vector {1.2, 2.3, 3.4, 4.5})); - CHECK(j4.get>() == (std::vector {true, false, true})); - CHECK(j5.get>() == (std::vector {"one", "two", "three"})); - } - - SECTION("std::deque") - { - CHECK(j1.get>() == (std::deque {1, 2, 3, 4})); - CHECK(j2.get>() == (std::deque {1u, 2u, 3u, 4u})); - CHECK(j3.get>() == (std::deque {1.2, 2.3, 3.4, 4.5})); - CHECK(j4.get>() == (std::deque {true, false, true})); - CHECK(j5.get>() == (std::deque {"one", "two", "three"})); - } - - SECTION("std::set") - { - CHECK(j1.get>() == (std::set {1, 2, 3, 4})); - CHECK(j2.get>() == (std::set {1u, 2u, 3u, 4u})); - CHECK(j3.get>() == (std::set {1.2, 2.3, 3.4, 4.5})); - CHECK(j4.get>() == (std::set {true, false, true})); - CHECK(j5.get>() == (std::set {"one", "two", "three"})); - } - - SECTION("std::unordered_set") - { - CHECK(j1.get>() == (std::unordered_set {1, 2, 3, 4})); - CHECK(j2.get>() == (std::unordered_set {1u, 2u, 3u, 4u})); - CHECK(j3.get>() == (std::unordered_set {1.2, 2.3, 3.4, 4.5})); - CHECK(j4.get>() == (std::unordered_set {true, false, true})); - CHECK(j5.get>() == (std::unordered_set {"one", "two", "three"})); - } - - SECTION("std::map (array of pairs)") - { - const std::map m{{0, 1}, {1, 2}, {2, 3}}; - json const j6 = m; - - auto m2 = j6.get>(); - CHECK(m == m2); - - json const j7 = {0, 1, 2, 3}; - json const j8 = 2; -#if JSON_DIAGNOSTICS - CHECK_THROWS_WITH_AS((j7.get>()), - "[json.exception.type_error.302] (/0) type must be array, " - "but is number", json::type_error&); -#else - CHECK_THROWS_WITH_AS((j7.get>()), - "[json.exception.type_error.302] type must be array, " - "but is number", json::type_error&); -#endif - CHECK_THROWS_WITH_AS((j8.get>()), - "[json.exception.type_error.302] type must be array, " - "but is number", json::type_error&); - - SECTION("superfluous entries") - { - json const j9 = {{0, 1, 2}, {1, 2, 3}, {2, 3, 4}}; - m2 = j9.get>(); - CHECK(m == m2); - } - } - - SECTION("std::unordered_map (array of pairs)") - { - const std::unordered_map m{{0, 1}, {1, 2}, {2, 3}}; - json const j6 = m; - - auto m2 = j6.get>(); - CHECK(m == m2); - - json const j7 = {0, 1, 2, 3}; - json const j8 = 2; -#if JSON_DIAGNOSTICS - CHECK_THROWS_WITH_AS((j7.get>()), - "[json.exception.type_error.302] (/0) type must be array, " - "but is number", json::type_error&); -#else - CHECK_THROWS_WITH_AS((j7.get>()), - "[json.exception.type_error.302] type must be array, " - "but is number", json::type_error&); -#endif - CHECK_THROWS_WITH_AS((j8.get>()), - "[json.exception.type_error.302] type must be array, " - "but is number", json::type_error&); - - SECTION("superfluous entries") - { - json const j9{{0, 1, 2}, {1, 2, 3}, {2, 3, 4}}; - m2 = j9.get>(); - CHECK(m == m2); - } - } - - SECTION("exception in case of a non-object type") - { - // does type really must be an array? or it rather must not be null? - // that's what I thought when other test like this one broke - CHECK_THROWS_WITH_AS( - (json().get>()), - "[json.exception.type_error.302] type must be array, but is null", json::type_error&); - CHECK_THROWS_WITH_AS( - (json().get>()), - "[json.exception.type_error.302] type must be array, but is null", json::type_error&); - CHECK_THROWS_WITH_AS( - (json().get>()), - "[json.exception.type_error.302] type must be array, but is null", json::type_error&); - CHECK_THROWS_WITH_AS( - (json().get>()), - "[json.exception.type_error.302] type must be array, but is null", json::type_error&); - CHECK_THROWS_WITH_AS( - (json().get>()), - "[json.exception.type_error.302] type must be array, but is null", json::type_error&); - CHECK_THROWS_WITH_AS( - (json().get>()), - "[json.exception.type_error.302] type must be array, but is null", json::type_error&); - } - } - } } -enum class cards {kreuz, pik, herz, karo}; - -// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness) - false positive -NLOHMANN_JSON_SERIALIZE_ENUM(cards, -{ - {cards::kreuz, "kreuz"}, - {cards::pik, "pik"}, - {cards::pik, "puk"}, // second entry for cards::puk; will not be used - {cards::herz, "herz"}, - {cards::karo, "karo"} -}) - -enum TaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class) -{ - TS_STOPPED, - TS_RUNNING, - TS_COMPLETED, - TS_INVALID = -1, -}; - -// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage) - false positive -NLOHMANN_JSON_SERIALIZE_ENUM(TaskState, -{ - {TS_INVALID, nullptr}, - {TS_STOPPED, "stopped"}, - {TS_RUNNING, "running"}, - {TS_COMPLETED, "completed"}, -}) - -TEST_CASE("JSON to enum mapping") -{ - SECTION("enum class") - { - // enum -> json - CHECK(json(cards::kreuz) == "kreuz"); - CHECK(json(cards::pik) == "pik"); - CHECK(json(cards::herz) == "herz"); - CHECK(json(cards::karo) == "karo"); - - // json -> enum - CHECK(cards::kreuz == json("kreuz")); - CHECK(cards::pik == json("pik")); - CHECK(cards::herz == json("herz")); - CHECK(cards::karo == json("karo")); - - // invalid json -> first enum - CHECK(cards::kreuz == json("what?").get()); - } - - SECTION("traditional enum") - { - // enum -> json - CHECK(json(TS_STOPPED) == "stopped"); - CHECK(json(TS_RUNNING) == "running"); - CHECK(json(TS_COMPLETED) == "completed"); - CHECK(json(TS_INVALID) == json()); - - // json -> enum - CHECK(TS_STOPPED == json("stopped")); - CHECK(TS_RUNNING == json("running")); - CHECK(TS_COMPLETED == json("completed")); - CHECK(TS_INVALID == json()); - - // invalid json -> first enum - CHECK(TS_INVALID == json("what?").get()); - } -} - -enum class strict_cards {kreuz, pik, herz, karo, andere}; // andere not included in mapping - -// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness) - false positive -NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_cards, -{ - {strict_cards::kreuz, "kreuz"}, - {strict_cards::pik, "pik"}, - {strict_cards::pik, "puk"}, // second entry for cards::pik; will not be used - {strict_cards::herz, "herz"}, - {strict_cards::karo, "karo"} -}) - -enum StrictTaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class) -{ - STRICT_TS_STOPPED, - STRICT_TS_RUNNING, - STRICT_TS_COMPLETED, - STRICT_TS_OTHER, // STRICT_TS_OTHER not in mapping - STRICT_TS_INVALID = -1, -}; - -// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage) - false positive -NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(StrictTaskState, -{ - {STRICT_TS_INVALID, nullptr}, - {STRICT_TS_STOPPED, "stopped"}, - {STRICT_TS_RUNNING, "running"}, - {STRICT_TS_COMPLETED, "completed"}, -}) - -// regression test for #5708 item 2: NLOHMANN_JSON_SERIALIZE_ENUM_STRICT must not rely on -// unqualified lookup of a helper name that a user's own namespace may also declare -namespace ns_with_colliding_name -{ -// NOLINTNEXTLINE(misc-use-internal-linkage) - used to shadow the library's internal helper name -inline void templated_json_throw(int /*unused*/) {} - -enum class colliding_enum { a, b }; - -// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive -NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(colliding_enum, -{ - {colliding_enum::a, "a"}, - {colliding_enum::b, "b"} -}) -} // namespace ns_with_colliding_name - -TEST_CASE("NLOHMANN_JSON_SERIALIZE_ENUM_STRICT in a namespace with a colliding name") -{ - using ns_with_colliding_name::colliding_enum; - - CHECK(json(colliding_enum::a) == "a"); - CHECK(colliding_enum::b == json("b")); - - json _; - CHECK_THROWS_WITH_AS(_ = json("nope").get(), "[json.exception.out_of_range.410] enum value out of range for colliding_enum: \"nope\"", json::out_of_range&); -} - -TEST_CASE("Strict JSON to enum mapping") -{ - SECTION("enum class") - { - // enum -> json - CHECK(json(strict_cards::kreuz) == "kreuz"); - CHECK(json(strict_cards::pik) == "pik"); - CHECK(json(strict_cards::herz) == "herz"); - CHECK(json(strict_cards::karo) == "karo"); - - // json -> enum - CHECK(json("kreuz").get() == strict_cards::kreuz); - CHECK(json("pik").get() == strict_cards::pik); - CHECK(json("herz").get() == strict_cards::herz); - CHECK(json("karo").get() == strict_cards::karo); - - // comparison of enum and json - CHECK(strict_cards::kreuz == json("kreuz")); - CHECK(strict_cards::pik == json("pik")); - CHECK(strict_cards::herz == json("herz")); - CHECK(strict_cards::karo == json("karo")); - - // invalid json -> exception thrown - json _; - CHECK_THROWS_WITH_AS(_ = json("what?").get(), "[json.exception.out_of_range.410] enum value out of range for strict_cards: \"what?\"", json::out_of_range&); - - // conversion of unmapped enum -> exception thrown - CHECK_THROWS_WITH_AS(json(strict_cards::andere), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&); - - // comparing an unmapped enum with json throws the same exception - // (the scalar comparison operators used to be noexcept, so this - // called std::terminate) - CHECK_THROWS_WITH_AS(static_cast(strict_cards::andere == json("andere")), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&); - CHECK_THROWS_WITH_AS(static_cast(json("andere") != strict_cards::andere), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&); - - // invalid UTF-8 -> out_of_range.410, not the type_error.316 thrown while building the - // message (regression test for #5667); such strings can reach get() unvalidated, - // e.g. from from_cbor()/from_msgpack() (#5529) - const json j_invalid_utf8 = "\xFF"; - CHECK_THROWS_WITH_AS(_ = j_invalid_utf8.get(), "[json.exception.out_of_range.410] enum value out of range for strict_cards: \"\xEF\xBF\xBD\"", json::out_of_range&); - } - - SECTION("traditional enum") - { - // enum -> json - CHECK(json(STRICT_TS_STOPPED) == "stopped"); - CHECK(json(STRICT_TS_RUNNING) == "running"); - CHECK(json(STRICT_TS_COMPLETED) == "completed"); - CHECK(json(STRICT_TS_INVALID) == json()); - - // json -> enum - CHECK(json("stopped").get() == STRICT_TS_STOPPED); - CHECK(json("running").get() == STRICT_TS_RUNNING); - CHECK(json("completed").get() == STRICT_TS_COMPLETED); - CHECK(json().get() == STRICT_TS_INVALID); - - // comparison of enum and json - CHECK(STRICT_TS_STOPPED == json("stopped")); - CHECK(STRICT_TS_RUNNING == json("running")); - CHECK(STRICT_TS_COMPLETED == json("completed")); - CHECK(STRICT_TS_INVALID == json()); - - // invalid json -> exception thrown - json _; - CHECK_THROWS_WITH_AS(_ = json("what?").get(), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState: \"what?\"", json::out_of_range&); - - // conversion of unmapped enum -> exception thrown - CHECK_THROWS_WITH_AS(json(STRICT_TS_OTHER), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState", json::out_of_range&); - - // comparing an unmapped enum with json throws the same exception - CHECK_THROWS_WITH_AS(static_cast(STRICT_TS_OTHER < json("x")), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState", json::out_of_range&); - } -} - - -#ifdef JSON_HAS_CPP_17 -#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM -TEST_CASE("std::filesystem::path") -{ - SECTION("ascii") - { - json const j_string = "Path"; - auto p = j_string.template get(); - json const j_path = p; - - CHECK(j_path.template get() == - j_string.template get()); - } - - SECTION("utf-8") - { - json const j_string = "P\xc4\x9b\xc5\xa1ina"; - auto p = j_string.template get(); - json const j_path = p; - - CHECK(j_path.template get() == - j_string.template get()); - } -} -#endif - -// the ADL to_json overload for std::u8string only exists under the same guard -// as std::filesystem::path support (it is otherwise only reached indirectly, -// via std::filesystem::path::u8string()) -- mirror both #if conditions from -// include/nlohmann/detail/conversions/to_json.hpp exactly -#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM -#if defined(__cpp_lib_char8_t) -TEST_CASE("std::u8string") -{ - SECTION("ascii") - { - const std::u8string s = u8"Path"; - json const j = s; - - CHECK(j.template get() == "Path"); - } - - SECTION("utf-8") - { - // use \u universal-character-names (rather than raw \x byte escapes - // or literal non-ASCII source bytes) to compose the multi-byte UTF-8 - // encoding -- MSVC treats \x escapes used that way inside a u8 - // literal as a nonstandard extension (warning C5321), which some of - // our CI configs promote to an error; \u is portable and produces - // the exact same encoded bytes without depending on the source - // file's encoding - const std::u8string s = u8"P\u011B\u0161ina"; - json const j = s; - - CHECK(j.template get() == "P\xc4\x9b\xc5\xa1ina"); - } -} -#endif -#endif - -#if !defined(JSON_NOEXCEPTION) -namespace -{ -// a type whose to_json reports an error by throwing, used below to check that -// converting a std::optional to JSON propagates an exception thrown while -// converting its contained value instead of calling std::terminate (#5642) -struct throwing_to_json_type {}; - -[[noreturn]] void to_json(json& /*unused*/, const throwing_to_json_type& /*unused*/) -{ - throw std::runtime_error("cannot serialize throwing_to_json_type"); -} -} // namespace -#endif - -TEST_CASE("std::optional") -{ - SECTION("null") - { - const json j_null; - const std::optional opt_null; - - CHECK(json(opt_null) == j_null); - CHECK(j_null.get>() == std::nullopt); - - // Constructing std::optional directly from JSON null throws because - // std::optional's own converting constructor is chosen over basic_json's - // operator T(). This is a language-level limitation (std::optional is - // constructible from T, and T is constructible from basic_json via the - // operator); there is no SFINAE path that distinguishes "call from inside - // std::optional's constructor" from "direct call". Use get>() - // or get_to() instead for correct null handling. See #4864 and #5246. - CHECK_THROWS_WITH_AS(std::optional(j_null), - "[json.exception.type_error.302] type must be string, but is null", json::type_error&); - CHECK_THROWS_WITH_AS(std::optional(j_null), - "[json.exception.type_error.302] type must be number, but is null", json::type_error&); - - // Assignment goes through the same overload resolution as direct - // construction, so it throws for the same reason. This relies on - // basic_json's implicit conversion operator, so it only applies - // when JSON_USE_IMPLICIT_CONVERSIONS is enabled (the default). -#if JSON_USE_IMPLICIT_CONVERSIONS - std::optional opt_assign; - CHECK_THROWS_WITH_AS(opt_assign = j_null, - "[json.exception.type_error.302] type must be string, but is null", json::type_error&); -#endif - - // get_to() is the correct way to obtain std::nullopt from a JSON null. - std::optional opt_get_to = "placeholder"; - j_null.get_to(opt_get_to); - CHECK(opt_get_to == std::nullopt); - } - - SECTION("string") - { - json j_string = "string"; - std::optional opt_string = "string"; - - CHECK(json(opt_string) == j_string); - CHECK(std::optional(j_string) == opt_string); - // false positive: Infer attributes the destruction of the temporaries above to opt_string - // @infer-ignore USE_AFTER_DELETE - } - - SECTION("bool") - { - json j_bool = true; - std::optional opt_bool = true; - - CHECK(json(opt_bool) == j_bool); - CHECK(std::optional(j_bool) == opt_bool); - } - - SECTION("number") - { - json j_number = 1; - std::optional opt_int = 1; - - CHECK(json(opt_int) == j_number); - CHECK(j_number.get>() == opt_int); - } - - SECTION("array") - { - json j_array = {1, 2, nullptr}; - std::vector> opt_array = {{1, 2, std::nullopt}}; - - CHECK(json(opt_array) == j_array); - CHECK(j_array.get>>() == opt_array); - } - - SECTION("object") - { - json j_object = {{"one", 1}, {"two", 2}, {"zero", nullptr}}; - std::map> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}}; - - CHECK(json(opt_object) == j_object); - CHECK(std::map>(j_object) == opt_object); - } - -#if !defined(JSON_NOEXCEPTION) - SECTION("exception from contained value's to_json propagates (#5642)") - { - // to_json(BasicJsonType&, const std::optional&) must not be - // noexcept: it calls T's to_json, which may throw (a user-defined - // to_json that reports an error, or std::bad_alloc for T = - // std::string/vector/json). Before the fix, this called - // std::terminate() instead of letting the exception propagate. - const std::optional opt = throwing_to_json_type{}; - CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&); - - // the conversion is noexcept exactly when converting the contained value is - static_assert(!std::is_nothrow_constructible&>::value); - static_assert(std::is_nothrow_constructible&>::value); - } -#endif -} -#endif - #ifdef JSON_HAS_CPP_17 #undef JSON_HAS_CPP_17 #endif diff --git a/tests/src/unit-conversions2.cpp b/tests/src/unit-conversions2.cpp new file mode 100644 index 000000000..eca482b83 --- /dev/null +++ b/tests/src/unit-conversions2.cpp @@ -0,0 +1,868 @@ +// __ _____ _____ _____ +// __| | __| | | | 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" + +// skip tests if JSON_DisableEnumSerialization=ON (#4384) +#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1) + #define SKIP_TESTS_FOR_ENUM_SERIALIZATION +#endif + +#define JSON_TESTS_PRIVATE +#include +using nlohmann::json; + +#include +#include +#include +#include +#include +#include +#include + +// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") + +#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 +#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 +#endif + +#ifdef JSON_HAS_CPP_17 + #if __has_include() + #include + #elif __has_include() + #include + #endif +#endif + +#if defined(JSON_HAS_CPP_17) + #include +#endif + +TEST_CASE("value conversion") +{ + SECTION("get a binary value (explicit)") + { + json::binary_t const n_reference{{1, 2, 3}}; + json j(n_reference); + + SECTION("binary_t") + { + json::binary_t const b = j.get(); + CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); + } + + SECTION("get_binary()") + { + SECTION("non-const") + { + auto& b = j.get_binary(); + CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); + } + + SECTION("non-const") + { + const json j_const = j; // NOLINT(performance-unnecessary-copy-initialization) + const auto& b = j_const.get_binary(); + CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); + } + } + + SECTION("exception in case of a non-string type") + { + json j_null(json::value_t::null); + json j_object(json::value_t::object); + json j_array(json::value_t::array); + json j_string(json::value_t::string); + json j_boolean(json::value_t::boolean); + const json j_null_const(json::value_t::null); + const json j_object_const(json::value_t::object); + const json j_array_const(json::value_t::array); + const json j_string_const(json::value_t::string); + const json j_boolean_const(json::value_t::boolean); + + CHECK_THROWS_WITH_AS(j_null.get(), + "[json.exception.type_error.302] type must be binary, but is null", + json::type_error&); + CHECK_THROWS_WITH_AS(j_object.get(), + "[json.exception.type_error.302] type must be binary, but is object", + json::type_error&); + CHECK_THROWS_WITH_AS(j_array.get(), + "[json.exception.type_error.302] type must be binary, but is array", + json::type_error&); + CHECK_THROWS_WITH_AS(j_string.get(), + "[json.exception.type_error.302] type must be binary, but is string", + json::type_error&); + CHECK_THROWS_WITH_AS(j_boolean.get(), + "[json.exception.type_error.302] type must be binary, but is boolean", + json::type_error&); + + CHECK_THROWS_WITH_AS(j_null_const.get(), + "[json.exception.type_error.302] type must be binary, but is null", + json::type_error&); + CHECK_THROWS_WITH_AS(j_object_const.get(), + "[json.exception.type_error.302] type must be binary, but is object", + json::type_error&); + CHECK_THROWS_WITH_AS(j_array_const.get(), + "[json.exception.type_error.302] type must be binary, but is array", + json::type_error&); + CHECK_THROWS_WITH_AS(j_string_const.get(), + "[json.exception.type_error.302] type must be binary, but is string", + json::type_error&); + CHECK_THROWS_WITH_AS(j_boolean_const.get(), + "[json.exception.type_error.302] type must be binary, but is boolean", + json::type_error&); + + CHECK_THROWS_WITH_AS(j_null.get_binary(), + "[json.exception.type_error.302] type must be binary, but is null", + json::type_error&); + CHECK_THROWS_WITH_AS(j_object.get_binary(), + "[json.exception.type_error.302] type must be binary, but is object", + json::type_error&); + CHECK_THROWS_WITH_AS(j_array.get_binary(), + "[json.exception.type_error.302] type must be binary, but is array", + json::type_error&); + CHECK_THROWS_WITH_AS(j_string.get_binary(), + "[json.exception.type_error.302] type must be binary, but is string", + json::type_error&); + CHECK_THROWS_WITH_AS(j_boolean.get_binary(), + "[json.exception.type_error.302] type must be binary, but is boolean", + json::type_error&); + + CHECK_THROWS_WITH_AS(j_null_const.get_binary(), + "[json.exception.type_error.302] type must be binary, but is null", + json::type_error&); + CHECK_THROWS_WITH_AS(j_object_const.get_binary(), + "[json.exception.type_error.302] type must be binary, but is object", + json::type_error&); + CHECK_THROWS_WITH_AS(j_array_const.get_binary(), + "[json.exception.type_error.302] type must be binary, but is array", + json::type_error&); + CHECK_THROWS_WITH_AS(j_string_const.get_binary(), + "[json.exception.type_error.302] type must be binary, but is string", + json::type_error&); + CHECK_THROWS_WITH_AS(j_boolean_const.get_binary(), + "[json.exception.type_error.302] type must be binary, but is boolean", + json::type_error&); + } + } + +#if JSON_USE_IMPLICIT_CONVERSIONS + SECTION("get a binary value (implicit)") + { + json::binary_t const n_reference{{1, 2, 3}}; + json const j(n_reference); + + SECTION("binary_t") + { + json::binary_t const b = j; + CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); + } + } +#endif + +#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION + SECTION("get an enum") + { + enum c_enum { value_1, value_2 }; // NOLINT(cppcoreguidelines-use-enum-class) + enum class cpp_enum { value_1, value_2 }; + + CHECK(json(value_1).get() == value_1); + CHECK(json(cpp_enum::value_1).get() == cpp_enum::value_1); + } + + SECTION("get an enum with underlying type bool (#5671)") + { + enum class bool_enum : bool { off, on }; + + CHECK(json(bool_enum::off).get() == bool_enum::off); + CHECK(json(bool_enum::on).get() == bool_enum::on); + } +#endif + + SECTION("more involved conversions") + { + SECTION("object-like STL containers") + { + json const j1 = {{"one", 1}, {"two", 2}, {"three", 3}}; + json const j2 = {{"one", 1u}, {"two", 2u}, {"three", 3u}}; + json const j3 = {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}; + json const j4 = {{"one", true}, {"two", false}, {"three", true}}; + json const j5 = {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}; + + SECTION("std::map") + { + CHECK(j1.get>() == (std::map {{"one", 1}, {"two", 2}, {"three", 3}})); + CHECK(j2.get>() == (std::map {{"one", 1u}, {"two", 2u}, {"three", 3u}})); + CHECK(j3.get>() == (std::map {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); + CHECK(j4.get>() == (std::map {{"one", true}, {"two", false}, {"three", true}})); + CHECK(j5.get>() == (std::map {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); + } + + SECTION("std::unordered_map") + { + CHECK(j1.get>() == (std::unordered_map {{"one", 1}, {"two", 2}, {"three", 3}})); + CHECK(j2.get>() == (std::unordered_map {{"one", 1u}, {"two", 2u}, {"three", 3u}})); + CHECK(j3.get>() == (std::unordered_map {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); + CHECK(j4.get>() == (std::unordered_map {{"one", true}, {"two", false}, {"three", true}})); + const auto m5 = j5.get>(); + CHECK(m5 == (std::unordered_map {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); + CHECK(m5.at("one") == "eins"); + } + + SECTION("reserve is called on containers that support it (#5406)") + { + // build a larger object so that a missing/incorrect reserve() + // call would be more likely to corrupt or drop elements + json j_large; + for (int i = 0; i < 100; ++i) + { + j_large[std::to_string(i)] = i; + } + + SECTION("std::unordered_map (supports reserve)") + { + const auto m = j_large.get>(); + CHECK(m.size() == 100); + for (int i = 0; i < 100; ++i) + { + CHECK(m.at(std::to_string(i)) == i); + } + } + + SECTION("std::map (no reserve, fallback path)") + { + const auto m = j_large.get>(); + CHECK(m.size() == 100); + for (int i = 0; i < 100; ++i) + { + CHECK(m.at(std::to_string(i)) == i); + } + } + } + + SECTION("std::multimap") + { + CHECK(j1.get>() == (std::multimap {{"one", 1}, {"two", 2}, {"three", 3}})); + CHECK(j2.get>() == (std::multimap {{"one", 1u}, {"two", 2u}, {"three", 3u}})); + CHECK(j3.get>() == (std::multimap {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); + CHECK(j4.get>() == (std::multimap {{"one", true}, {"two", false}, {"three", true}})); + const auto m5 = j5.get>(); + CHECK(m5 == (std::multimap {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); + CHECK(m5.find("one")->second == "eins"); + } + + SECTION("std::unordered_multimap") + { + CHECK(j1.get>() == (std::unordered_multimap {{"one", 1}, {"two", 2}, {"three", 3}})); + CHECK(j2.get>() == (std::unordered_multimap {{"one", 1u}, {"two", 2u}, {"three", 3u}})); + CHECK(j3.get>() == (std::unordered_multimap {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); + CHECK(j4.get>() == (std::unordered_multimap {{"one", true}, {"two", false}, {"three", true}})); + const auto m5 = j5.get>(); + CHECK(m5 == (std::unordered_multimap {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); + CHECK(m5.find("one")->second == "eins"); + } + + SECTION("exception in case of a non-object type") + { + CHECK_THROWS_WITH_AS( + (json().get>()), + "[json.exception.type_error.302] type must be object, but is null", json::type_error&); + } + } + + SECTION("array-like STL containers") + { + json const j1 = {1, 2, 3, 4}; + json const j2 = {1u, 2u, 3u, 4u}; + json const j3 = {1.2, 2.3, 3.4, 4.5}; + json const j4 = {true, false, true}; + json const j5 = {"one", "two", "three"}; + + SECTION("std::list") + { + CHECK(j1.get>() == (std::list {1, 2, 3, 4})); + CHECK(j2.get>() == (std::list {1u, 2u, 3u, 4u})); + CHECK(j3.get>() == (std::list {1.2, 2.3, 3.4, 4.5})); + CHECK(j4.get>() == (std::list {true, false, true})); + CHECK(j5.get>() == (std::list {"one", "two", "three"})); + } + + SECTION("std::forward_list") + { + CHECK(j1.get>() == (std::forward_list {1, 2, 3, 4})); + CHECK(j2.get>() == (std::forward_list {1u, 2u, 3u, 4u})); + CHECK(j3.get>() == (std::forward_list {1.2, 2.3, 3.4, 4.5})); + CHECK(j4.get>() == (std::forward_list {true, false, true})); + CHECK(j5.get>() == (std::forward_list {"one", "two", "three"})); + } + + SECTION("std::array") + { + CHECK(j1.get>() == (std::array {{1, 2, 3, 4}})); + // only the first 3 elements of j2 are converted, since the target array is smaller + CHECK(j2.get>() == (std::array {{1u, 2u, 3u}})); + CHECK(j3.get>() == (std::array {{1.2, 2.3, 3.4, 4.5}})); + CHECK(j4.get>() == (std::array {{true, false, true}})); + CHECK(j5.get>() == (std::array {{"one", "two", "three"}})); + + SECTION("std::array is larger than JSON") + { + std::array arr6 = {{1, 2, 3, 4, 5, 6}}; + CHECK_THROWS_WITH_AS(j1.get_to(arr6), "[json.exception.out_of_range.401] " + "array index 4 is out of range", json::out_of_range&); + } + + SECTION("std::array is smaller than JSON") + { + std::array arr2 = {{8, 9}}; + j1.get_to(arr2); + CHECK(arr2[0] == 1); + CHECK(arr2[1] == 2); + } + } + + SECTION("std::valarray") + { + // valarray has no operator== that returns bool, so compare via a vector copy + const auto v1 = j1.get>(); + CHECK((std::vector(std::begin(v1), std::end(v1)) == std::vector {1, 2, 3, 4})); + const auto v2 = j2.get>(); + CHECK((std::vector(std::begin(v2), std::end(v2)) == std::vector {1u, 2u, 3u, 4u})); + const auto v3 = j3.get>(); + CHECK((std::vector(std::begin(v3), std::end(v3)) == std::vector {1.2, 2.3, 3.4, 4.5})); + const auto v4 = j4.get>(); + CHECK((std::vector(std::begin(v4), std::end(v4)) == std::vector {true, false, true})); + const auto v5 = j5.get>(); + CHECK((std::vector(std::begin(v5), std::end(v5)) == std::vector {"one", "two", "three"})); + } + + SECTION("std::vector") + { + CHECK(j1.get>() == (std::vector {1, 2, 3, 4})); + CHECK(j2.get>() == (std::vector {1u, 2u, 3u, 4u})); + CHECK(j3.get>() == (std::vector {1.2, 2.3, 3.4, 4.5})); + CHECK(j4.get>() == (std::vector {true, false, true})); + CHECK(j5.get>() == (std::vector {"one", "two", "three"})); + } + + SECTION("std::deque") + { + CHECK(j1.get>() == (std::deque {1, 2, 3, 4})); + CHECK(j2.get>() == (std::deque {1u, 2u, 3u, 4u})); + CHECK(j3.get>() == (std::deque {1.2, 2.3, 3.4, 4.5})); + CHECK(j4.get>() == (std::deque {true, false, true})); + CHECK(j5.get>() == (std::deque {"one", "two", "three"})); + } + + SECTION("std::set") + { + CHECK(j1.get>() == (std::set {1, 2, 3, 4})); + CHECK(j2.get>() == (std::set {1u, 2u, 3u, 4u})); + CHECK(j3.get>() == (std::set {1.2, 2.3, 3.4, 4.5})); + CHECK(j4.get>() == (std::set {true, false, true})); + CHECK(j5.get>() == (std::set {"one", "two", "three"})); + } + + SECTION("std::unordered_set") + { + CHECK(j1.get>() == (std::unordered_set {1, 2, 3, 4})); + CHECK(j2.get>() == (std::unordered_set {1u, 2u, 3u, 4u})); + CHECK(j3.get>() == (std::unordered_set {1.2, 2.3, 3.4, 4.5})); + CHECK(j4.get>() == (std::unordered_set {true, false, true})); + CHECK(j5.get>() == (std::unordered_set {"one", "two", "three"})); + } + + SECTION("std::map (array of pairs)") + { + const std::map m{{0, 1}, {1, 2}, {2, 3}}; + json const j6 = m; + + auto m2 = j6.get>(); + CHECK(m == m2); + + json const j7 = {0, 1, 2, 3}; + json const j8 = 2; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS((j7.get>()), + "[json.exception.type_error.302] (/0) type must be array, " + "but is number", json::type_error&); +#else + CHECK_THROWS_WITH_AS((j7.get>()), + "[json.exception.type_error.302] type must be array, " + "but is number", json::type_error&); +#endif + CHECK_THROWS_WITH_AS((j8.get>()), + "[json.exception.type_error.302] type must be array, " + "but is number", json::type_error&); + + SECTION("superfluous entries") + { + json const j9 = {{0, 1, 2}, {1, 2, 3}, {2, 3, 4}}; + m2 = j9.get>(); + CHECK(m == m2); + } + } + + SECTION("std::unordered_map (array of pairs)") + { + const std::unordered_map m{{0, 1}, {1, 2}, {2, 3}}; + json const j6 = m; + + auto m2 = j6.get>(); + CHECK(m == m2); + + json const j7 = {0, 1, 2, 3}; + json const j8 = 2; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS((j7.get>()), + "[json.exception.type_error.302] (/0) type must be array, " + "but is number", json::type_error&); +#else + CHECK_THROWS_WITH_AS((j7.get>()), + "[json.exception.type_error.302] type must be array, " + "but is number", json::type_error&); +#endif + CHECK_THROWS_WITH_AS((j8.get>()), + "[json.exception.type_error.302] type must be array, " + "but is number", json::type_error&); + + SECTION("superfluous entries") + { + json const j9{{0, 1, 2}, {1, 2, 3}, {2, 3, 4}}; + m2 = j9.get>(); + CHECK(m == m2); + } + } + + SECTION("exception in case of a non-object type") + { + // does type really must be an array? or it rather must not be null? + // that's what I thought when other test like this one broke + CHECK_THROWS_WITH_AS( + (json().get>()), + "[json.exception.type_error.302] type must be array, but is null", json::type_error&); + CHECK_THROWS_WITH_AS( + (json().get>()), + "[json.exception.type_error.302] type must be array, but is null", json::type_error&); + CHECK_THROWS_WITH_AS( + (json().get>()), + "[json.exception.type_error.302] type must be array, but is null", json::type_error&); + CHECK_THROWS_WITH_AS( + (json().get>()), + "[json.exception.type_error.302] type must be array, but is null", json::type_error&); + CHECK_THROWS_WITH_AS( + (json().get>()), + "[json.exception.type_error.302] type must be array, but is null", json::type_error&); + CHECK_THROWS_WITH_AS( + (json().get>()), + "[json.exception.type_error.302] type must be array, but is null", json::type_error&); + } + } + } +} + +enum class cards {kreuz, pik, herz, karo}; + +// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness) - false positive +NLOHMANN_JSON_SERIALIZE_ENUM(cards, +{ + {cards::kreuz, "kreuz"}, + {cards::pik, "pik"}, + {cards::pik, "puk"}, // second entry for cards::puk; will not be used + {cards::herz, "herz"}, + {cards::karo, "karo"} +}) + +enum TaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class) +{ + TS_STOPPED, + TS_RUNNING, + TS_COMPLETED, + TS_INVALID = -1, +}; + +// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage) - false positive +NLOHMANN_JSON_SERIALIZE_ENUM(TaskState, +{ + {TS_INVALID, nullptr}, + {TS_STOPPED, "stopped"}, + {TS_RUNNING, "running"}, + {TS_COMPLETED, "completed"}, +}) + +TEST_CASE("JSON to enum mapping") +{ + SECTION("enum class") + { + // enum -> json + CHECK(json(cards::kreuz) == "kreuz"); + CHECK(json(cards::pik) == "pik"); + CHECK(json(cards::herz) == "herz"); + CHECK(json(cards::karo) == "karo"); + + // json -> enum + CHECK(cards::kreuz == json("kreuz")); + CHECK(cards::pik == json("pik")); + CHECK(cards::herz == json("herz")); + CHECK(cards::karo == json("karo")); + + // invalid json -> first enum + CHECK(cards::kreuz == json("what?").get()); + } + + SECTION("traditional enum") + { + // enum -> json + CHECK(json(TS_STOPPED) == "stopped"); + CHECK(json(TS_RUNNING) == "running"); + CHECK(json(TS_COMPLETED) == "completed"); + CHECK(json(TS_INVALID) == json()); + + // json -> enum + CHECK(TS_STOPPED == json("stopped")); + CHECK(TS_RUNNING == json("running")); + CHECK(TS_COMPLETED == json("completed")); + CHECK(TS_INVALID == json()); + + // invalid json -> first enum + CHECK(TS_INVALID == json("what?").get()); + } +} + +enum class strict_cards {kreuz, pik, herz, karo, andere}; // andere not included in mapping + +// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness) - false positive +NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_cards, +{ + {strict_cards::kreuz, "kreuz"}, + {strict_cards::pik, "pik"}, + {strict_cards::pik, "puk"}, // second entry for cards::pik; will not be used + {strict_cards::herz, "herz"}, + {strict_cards::karo, "karo"} +}) + +enum StrictTaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class) +{ + STRICT_TS_STOPPED, + STRICT_TS_RUNNING, + STRICT_TS_COMPLETED, + STRICT_TS_OTHER, // STRICT_TS_OTHER not in mapping + STRICT_TS_INVALID = -1, +}; + +// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage) - false positive +NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(StrictTaskState, +{ + {STRICT_TS_INVALID, nullptr}, + {STRICT_TS_STOPPED, "stopped"}, + {STRICT_TS_RUNNING, "running"}, + {STRICT_TS_COMPLETED, "completed"}, +}) + +// regression test for #5708 item 2: NLOHMANN_JSON_SERIALIZE_ENUM_STRICT must not rely on +// unqualified lookup of a helper name that a user's own namespace may also declare +namespace ns_with_colliding_name +{ +// NOLINTNEXTLINE(misc-use-internal-linkage) - used to shadow the library's internal helper name +inline void templated_json_throw(int /*unused*/) {} + +enum class colliding_enum { a, b }; + +// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive +NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(colliding_enum, +{ + {colliding_enum::a, "a"}, + {colliding_enum::b, "b"} +}) +} // namespace ns_with_colliding_name + +TEST_CASE("NLOHMANN_JSON_SERIALIZE_ENUM_STRICT in a namespace with a colliding name") +{ + using ns_with_colliding_name::colliding_enum; + + CHECK(json(colliding_enum::a) == "a"); + CHECK(colliding_enum::b == json("b")); + + json _; + CHECK_THROWS_WITH_AS(_ = json("nope").get(), "[json.exception.out_of_range.410] enum value out of range for colliding_enum: \"nope\"", json::out_of_range&); +} + +TEST_CASE("Strict JSON to enum mapping") +{ + SECTION("enum class") + { + // enum -> json + CHECK(json(strict_cards::kreuz) == "kreuz"); + CHECK(json(strict_cards::pik) == "pik"); + CHECK(json(strict_cards::herz) == "herz"); + CHECK(json(strict_cards::karo) == "karo"); + + // json -> enum + CHECK(json("kreuz").get() == strict_cards::kreuz); + CHECK(json("pik").get() == strict_cards::pik); + CHECK(json("herz").get() == strict_cards::herz); + CHECK(json("karo").get() == strict_cards::karo); + + // comparison of enum and json + CHECK(strict_cards::kreuz == json("kreuz")); + CHECK(strict_cards::pik == json("pik")); + CHECK(strict_cards::herz == json("herz")); + CHECK(strict_cards::karo == json("karo")); + + // invalid json -> exception thrown + json _; + CHECK_THROWS_WITH_AS(_ = json("what?").get(), "[json.exception.out_of_range.410] enum value out of range for strict_cards: \"what?\"", json::out_of_range&); + + // conversion of unmapped enum -> exception thrown + CHECK_THROWS_WITH_AS(json(strict_cards::andere), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&); + + // comparing an unmapped enum with json throws the same exception + // (the scalar comparison operators used to be noexcept, so this + // called std::terminate) + CHECK_THROWS_WITH_AS(static_cast(strict_cards::andere == json("andere")), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&); + CHECK_THROWS_WITH_AS(static_cast(json("andere") != strict_cards::andere), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&); + + // invalid UTF-8 -> out_of_range.410, not the type_error.316 thrown while building the + // message (regression test for #5667); such strings can reach get() unvalidated, + // e.g. from from_cbor()/from_msgpack() (#5529) + const json j_invalid_utf8 = "\xFF"; + CHECK_THROWS_WITH_AS(_ = j_invalid_utf8.get(), "[json.exception.out_of_range.410] enum value out of range for strict_cards: \"\xEF\xBF\xBD\"", json::out_of_range&); + } + + SECTION("traditional enum") + { + // enum -> json + CHECK(json(STRICT_TS_STOPPED) == "stopped"); + CHECK(json(STRICT_TS_RUNNING) == "running"); + CHECK(json(STRICT_TS_COMPLETED) == "completed"); + CHECK(json(STRICT_TS_INVALID) == json()); + + // json -> enum + CHECK(json("stopped").get() == STRICT_TS_STOPPED); + CHECK(json("running").get() == STRICT_TS_RUNNING); + CHECK(json("completed").get() == STRICT_TS_COMPLETED); + CHECK(json().get() == STRICT_TS_INVALID); + + // comparison of enum and json + CHECK(STRICT_TS_STOPPED == json("stopped")); + CHECK(STRICT_TS_RUNNING == json("running")); + CHECK(STRICT_TS_COMPLETED == json("completed")); + CHECK(STRICT_TS_INVALID == json()); + + // invalid json -> exception thrown + json _; + CHECK_THROWS_WITH_AS(_ = json("what?").get(), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState: \"what?\"", json::out_of_range&); + + // conversion of unmapped enum -> exception thrown + CHECK_THROWS_WITH_AS(json(STRICT_TS_OTHER), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState", json::out_of_range&); + + // comparing an unmapped enum with json throws the same exception + CHECK_THROWS_WITH_AS(static_cast(STRICT_TS_OTHER < json("x")), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState", json::out_of_range&); + } +} + + +#ifdef JSON_HAS_CPP_17 +#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM +TEST_CASE("std::filesystem::path") +{ + SECTION("ascii") + { + json const j_string = "Path"; + auto p = j_string.template get(); + json const j_path = p; + + CHECK(j_path.template get() == + j_string.template get()); + } + + SECTION("utf-8") + { + json const j_string = "P\xc4\x9b\xc5\xa1ina"; + auto p = j_string.template get(); + json const j_path = p; + + CHECK(j_path.template get() == + j_string.template get()); + } +} +#endif + +// the ADL to_json overload for std::u8string only exists under the same guard +// as std::filesystem::path support (it is otherwise only reached indirectly, +// via std::filesystem::path::u8string()) -- mirror both #if conditions from +// include/nlohmann/detail/conversions/to_json.hpp exactly +#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM +#if defined(__cpp_lib_char8_t) +TEST_CASE("std::u8string") +{ + SECTION("ascii") + { + const std::u8string s = u8"Path"; + json const j = s; + + CHECK(j.template get() == "Path"); + } + + SECTION("utf-8") + { + // use \u universal-character-names (rather than raw \x byte escapes + // or literal non-ASCII source bytes) to compose the multi-byte UTF-8 + // encoding -- MSVC treats \x escapes used that way inside a u8 + // literal as a nonstandard extension (warning C5321), which some of + // our CI configs promote to an error; \u is portable and produces + // the exact same encoded bytes without depending on the source + // file's encoding + const std::u8string s = u8"P\u011B\u0161ina"; + json const j = s; + + CHECK(j.template get() == "P\xc4\x9b\xc5\xa1ina"); + } +} +#endif +#endif + +#if !defined(JSON_NOEXCEPTION) +namespace +{ +// a type whose to_json reports an error by throwing, used below to check that +// converting a std::optional to JSON propagates an exception thrown while +// converting its contained value instead of calling std::terminate (#5642) +struct throwing_to_json_type {}; + +[[noreturn]] void to_json(json& /*unused*/, const throwing_to_json_type& /*unused*/) +{ + throw std::runtime_error("cannot serialize throwing_to_json_type"); +} +} // namespace +#endif + +TEST_CASE("std::optional") +{ + SECTION("null") + { + const json j_null; + const std::optional opt_null; + + CHECK(json(opt_null) == j_null); + CHECK(j_null.get>() == std::nullopt); + + // Constructing std::optional directly from JSON null throws because + // std::optional's own converting constructor is chosen over basic_json's + // operator T(). This is a language-level limitation (std::optional is + // constructible from T, and T is constructible from basic_json via the + // operator); there is no SFINAE path that distinguishes "call from inside + // std::optional's constructor" from "direct call". Use get>() + // or get_to() instead for correct null handling. See #4864 and #5246. + CHECK_THROWS_WITH_AS(std::optional(j_null), + "[json.exception.type_error.302] type must be string, but is null", json::type_error&); + CHECK_THROWS_WITH_AS(std::optional(j_null), + "[json.exception.type_error.302] type must be number, but is null", json::type_error&); + + // Assignment goes through the same overload resolution as direct + // construction, so it throws for the same reason. This relies on + // basic_json's implicit conversion operator, so it only applies + // when JSON_USE_IMPLICIT_CONVERSIONS is enabled (the default). +#if JSON_USE_IMPLICIT_CONVERSIONS + std::optional opt_assign; + CHECK_THROWS_WITH_AS(opt_assign = j_null, + "[json.exception.type_error.302] type must be string, but is null", json::type_error&); +#endif + + // get_to() is the correct way to obtain std::nullopt from a JSON null. + std::optional opt_get_to = "placeholder"; + j_null.get_to(opt_get_to); + CHECK(opt_get_to == std::nullopt); + } + + SECTION("string") + { + json j_string = "string"; + std::optional opt_string = "string"; + + CHECK(json(opt_string) == j_string); + CHECK(std::optional(j_string) == opt_string); + // false positive: Infer attributes the destruction of the temporaries above to opt_string + // @infer-ignore USE_AFTER_DELETE + } + + SECTION("bool") + { + json j_bool = true; + std::optional opt_bool = true; + + CHECK(json(opt_bool) == j_bool); + CHECK(std::optional(j_bool) == opt_bool); + } + + SECTION("number") + { + json j_number = 1; + std::optional opt_int = 1; + + CHECK(json(opt_int) == j_number); + CHECK(j_number.get>() == opt_int); + } + + SECTION("array") + { + json j_array = {1, 2, nullptr}; + std::vector> opt_array = {{1, 2, std::nullopt}}; + + CHECK(json(opt_array) == j_array); + CHECK(j_array.get>>() == opt_array); + } + + SECTION("object") + { + json j_object = {{"one", 1}, {"two", 2}, {"zero", nullptr}}; + std::map> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}}; + + CHECK(json(opt_object) == j_object); + CHECK(std::map>(j_object) == opt_object); + } + +#if !defined(JSON_NOEXCEPTION) + SECTION("exception from contained value's to_json propagates (#5642)") + { + // to_json(BasicJsonType&, const std::optional&) must not be + // noexcept: it calls T's to_json, which may throw (a user-defined + // to_json that reports an error, or std::bad_alloc for T = + // std::string/vector/json). Before the fix, this called + // std::terminate() instead of letting the exception propagate. + const std::optional opt = throwing_to_json_type{}; + CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&); + + // the conversion is noexcept exactly when converting the contained value is + static_assert(!std::is_nothrow_constructible&>::value); + static_assert(std::is_nothrow_constructible&>::value); + } +#endif +} +#endif + +#ifdef JSON_HAS_CPP_17 + #undef JSON_HAS_CPP_17 +#endif + +#ifdef JSON_HAS_CPP_14 + #undef JSON_HAS_CPP_14 +#endif +DOCTEST_CLANG_SUPPRESS_WARNING_POP