From 103b37aa7d033f4da313fba5a9826bbc4a9dee0d Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 28 Sep 2026 02:14:19 +0200 Subject: [PATCH] Move the default enum-keyed map tests out of unit-conversions.cpp The Windows clang 20.1.8 job (MinGW, Debug) failed to link test-conversions_cpp17 with "relocation truncated to fit: IMAGE_REL_AMD64_REL32 against .rdata": the object file of unit-conversions.cpp was already close to the limit, and the new "maps with enum keys" test case pushed it over. windows.yml asks to keep these objects small by splitting test files. Move the test case unchanged into unit-enum_keyed_maps_default.cpp, with the three enums it needs. It still honors a -D flag for JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS, as before. unit-conversions.cpp is back to its state on develop. Signed-off-by: Niels Lohmann --- tests/src/unit-conversions.cpp | 73 ----------- tests/src/unit-enum_keyed_maps.cpp | 2 +- tests/src/unit-enum_keyed_maps_default.cpp | 141 +++++++++++++++++++++ 3 files changed, 142 insertions(+), 74 deletions(-) create mode 100644 tests/src/unit-enum_keyed_maps_default.cpp 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