diff --git a/tests/src/unit-conversions.cpp b/tests/src/unit-conversions.cpp index 0352a2ab5..90d972f71 100644 --- a/tests/src/unit-conversions.cpp +++ b/tests/src/unit-conversions.cpp @@ -26,7 +26,6 @@ using nlohmann::json; #include #include -#include #include #include #include @@ -1766,78 +1765,6 @@ TEST_CASE("Strict JSON to enum mapping") } } -namespace -{ -// std::hash is only required for enums since C++14 -struct enum_hash -{ - template - std::size_t operator()(T t) const noexcept - { - return static_cast(t); - } -}; -} // namespace - -// see unit-enum_keyed_maps.cpp for JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS=1 -TEST_CASE("maps with enum keys") -{ - using task_map = std::map; - using task_umap = std::unordered_map; - using task_gmap = std::map>; - using nested_map = std::map>; - using strict_map = std::map; - using int_map = std::map; - using int_umap = std::unordered_map; - - const task_map m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}}; - -#if !JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS - SECTION("stored as array of pairs") - { - CHECK(json(m) == json::parse(R"([["stopped","aa"],["completed","bb"]])")); - CHECK(json(task_umap {{TS_RUNNING, "cc"}}) == json::parse(R"([["running","cc"]])")); - } -#endif - - SECTION("read from array of pairs") - { - CHECK(json::parse(R"([["stopped","aa"],["completed","bb"]])").get() == m); - } - - SECTION("read from object (#4378)") - { - const json j = json::parse(R"({"stopped":"aa","completed":"bb"})"); - CHECK(j.get() == m); - CHECK(j.get() == task_umap(m.begin(), m.end())); - CHECK(j.get() == task_gmap(m.begin(), m.end())); - CHECK(json::parse(R"({"kreuz":{"stopped":1}})").get() == nested_map {{cards::kreuz, {{TS_STOPPED, 1}}}}); - CHECK(nlohmann::ordered_json::parse(R"({"stopped":"aa","completed":"bb"})").get() == m); - - // object keys go through the enum's from_json - strict_map sm; - CHECK_THROWS_WITH_AS(json::parse(R"({"what?":1})").get_to(sm), - "[json.exception.out_of_range.410] enum value out of range for strict_cards: \"what?\"", json::out_of_range&); - } - - SECTION("objects are only read for enum keys") - { - int_map im; - int_umap ium; - CHECK_THROWS_WITH_AS(json::parse(R"({"1":2})").get_to(im), - "[json.exception.type_error.302] type must be array, but is object", json::type_error&); - CHECK_THROWS_WITH_AS(json::parse(R"({"1":2})").get_to(ium), - "[json.exception.type_error.302] type must be array, but is object", json::type_error&); - } - - SECTION("other types are rejected") - { - task_map tm; - CHECK_THROWS_WITH_AS(json("stopped").get_to(tm), - "[json.exception.type_error.302] type must be array, but is string", json::type_error&); - } -} - #ifdef JSON_HAS_CPP_17 #if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM diff --git a/tests/src/unit-enum_keyed_maps.cpp b/tests/src/unit-enum_keyed_maps.cpp index 33228a2a7..a6ced6711 100644 --- a/tests/src/unit-enum_keyed_maps.cpp +++ b/tests/src/unit-enum_keyed_maps.cpp @@ -15,7 +15,7 @@ // This file tests the opt-in JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS, so it defines // the macro itself rather than relying on a -D flag, and runs in every build. -// The default behavior is tested in unit-conversions.cpp. +// The default behavior is tested in unit-enum_keyed_maps_default.cpp. #ifdef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS #undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS #endif diff --git a/tests/src/unit-enum_keyed_maps_default.cpp b/tests/src/unit-enum_keyed_maps_default.cpp new file mode 100644 index 000000000..899a0a0fb --- /dev/null +++ b/tests/src/unit-enum_keyed_maps_default.cpp @@ -0,0 +1,141 @@ +// __ _____ _____ _____ +// __| | __| | | | 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 + +#include "doctest_compatibility.h" + +// This file tests maps with enum keys with the default setting of +// JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS (or whatever a -D flag sets it to). +// unit-enum_keyed_maps.cpp tests JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS=1. +// These tests are not part of unit-conversions.cpp, because that object file +// is already too big for the MinGW linker of some compilers. + +#include +using nlohmann::json; + +#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") + +enum class cards {kreuz, pik, herz, karo}; + +// 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(cards, +{ + {cards::kreuz, "kreuz"}, + {cards::pik, "pik"}, + {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,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive +NLOHMANN_JSON_SERIALIZE_ENUM(TaskState, +{ + {TS_INVALID, nullptr}, + {TS_STOPPED, "stopped"}, + {TS_RUNNING, "running"}, + {TS_COMPLETED, "completed"}, +}) + +enum class strict_cards {kreuz, pik, herz, karo, andere}; // andere not included in mapping + +// 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(strict_cards, +{ + {strict_cards::kreuz, "kreuz"}, + {strict_cards::pik, "pik"}, + {strict_cards::herz, "herz"}, + {strict_cards::karo, "karo"} +}) + +namespace +{ +// std::hash is only required for enums since C++14 +struct enum_hash +{ + template + std::size_t operator()(T t) const noexcept + { + return static_cast(t); + } +}; +} // namespace + +// see unit-enum_keyed_maps.cpp for JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS=1 +TEST_CASE("maps with enum keys") +{ + using task_map = std::map; + using task_umap = std::unordered_map; + using task_gmap = std::map>; + using nested_map = std::map>; + using strict_map = std::map; + using int_map = std::map; + using int_umap = std::unordered_map; + + const task_map m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}}; + +#if !JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS + SECTION("stored as array of pairs") + { + CHECK(json(m) == json::parse(R"([["stopped","aa"],["completed","bb"]])")); + CHECK(json(task_umap {{TS_RUNNING, "cc"}}) == json::parse(R"([["running","cc"]])")); + } +#endif + + SECTION("read from array of pairs") + { + CHECK(json::parse(R"([["stopped","aa"],["completed","bb"]])").get() == m); + } + + SECTION("read from object (#4378)") + { + const json j = json::parse(R"({"stopped":"aa","completed":"bb"})"); + CHECK(j.get() == m); + CHECK(j.get() == task_umap(m.begin(), m.end())); + CHECK(j.get() == task_gmap(m.begin(), m.end())); + CHECK(json::parse(R"({"kreuz":{"stopped":1}})").get() == nested_map {{cards::kreuz, {{TS_STOPPED, 1}}}}); + CHECK(nlohmann::ordered_json::parse(R"({"stopped":"aa","completed":"bb"})").get() == m); + + // object keys go through the enum's from_json + strict_map sm; + CHECK_THROWS_WITH_AS(json::parse(R"({"what?":1})").get_to(sm), + "[json.exception.out_of_range.410] enum value out of range for strict_cards: \"what?\"", json::out_of_range&); + } + + SECTION("objects are only read for enum keys") + { + int_map im; + int_umap ium; + CHECK_THROWS_WITH_AS(json::parse(R"({"1":2})").get_to(im), + "[json.exception.type_error.302] type must be array, but is object", json::type_error&); + CHECK_THROWS_WITH_AS(json::parse(R"({"1":2})").get_to(ium), + "[json.exception.type_error.302] type must be array, but is object", json::type_error&); + } + + SECTION("other types are rejected") + { + task_map tm; + CHECK_THROWS_WITH_AS(json("stopped").get_to(tm), + "[json.exception.type_error.302] type must be array, but is string", json::type_error&); + } +} + +DOCTEST_CLANG_SUPPRESS_WARNING_POP