From 99716a5adec92ecd45e04b2187d296285b430e22 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 27 Sep 2026 18:24:48 +0200 Subject: [PATCH] Keep multimaps with enum keys as arrays of pairs With JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS, is_enum_keyed_map also matched std::multimap and std::unordered_multimap. Storing them as objects throws type_error.318 as soon as a key occurs twice, which is the normal case for a multimap, so such values could no longer be serialized at all once the macro was enabled, although they are stored losslessly as arrays of [key, value] pairs without it. Exclude maps with non-unique keys from is_enum_keyed_map. They are detected by insert(value_type) returning an iterator rather than a pair. Map-like types without such an insert() are still treated as before. Signed-off-by: Niels Lohmann --- .../macros/json_use_objects_for_enum_keyed_maps.md | 5 +++++ include/nlohmann/detail/meta/type_traits.hpp | 12 +++++++++++- single_include/nlohmann/json.hpp | 12 +++++++++++- tests/src/unit-enum_keyed_maps.cpp | 8 ++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/docs/mkdocs/docs/api/macros/json_use_objects_for_enum_keyed_maps.md b/docs/mkdocs/docs/api/macros/json_use_objects_for_enum_keyed_maps.md index d8218b8ec..a84d83129 100644 --- a/docs/mkdocs/docs/api/macros/json_use_objects_for_enum_keyed_maps.md +++ b/docs/mkdocs/docs/api/macros/json_use_objects_for_enum_keyed_maps.md @@ -35,6 +35,11 @@ The default value is `0` (disabled — existing behavior is preserved). {"completed": "bb", "stopped": "aa"} ``` +!!! note "Maps with non-unique keys" + + Maps that allow duplicate keys, such as `std::multimap` or `std::unordered_multimap`, are not affected + by the macro and are still stored as arrays of `[key, value]` pairs, as an object cannot hold duplicate keys. + !!! note "Reading" Reading is not affected by the macro: a map with enum keys can always be read from both an array of pairs and an diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index b27d6b5b1..c97947d16 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -400,6 +400,12 @@ template struct is_compatible_object_type : is_compatible_object_type_impl {}; +template +using insert_result_t = decltype(std::declval().insert(std::declval&>())); + +template +using insert_result_second_t = decltype(std::declval().insert(std::declval&>()).second); + // a map-like type (std::map, std::unordered_map, ...) whose keys are enums; see // JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS template @@ -410,8 +416,12 @@ struct is_enum_keyed_map < T, enable_if_t < is_detected::value&& is_detected::value >> { + // maps with non-unique keys (std::multimap, std::unordered_multimap, ...) + // are excluded, because an object cannot hold duplicate keys; they are + // detected by insert() returning an iterator instead of a pair // NOLINTNEXTLINE(modernize-type-traits) we use C++11 - static constexpr bool value = std::is_enum::value; + static constexpr bool value = std::is_enum::value && + !(is_detected::value && !is_detected::value); }; template struct is_compatible_object_type : is_compatible_object_type_impl {}; +template +using insert_result_t = decltype(std::declval().insert(std::declval&>())); + +template +using insert_result_second_t = decltype(std::declval().insert(std::declval&>()).second); + // a map-like type (std::map, std::unordered_map, ...) whose keys are enums; see // JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS template @@ -4431,8 +4437,12 @@ struct is_enum_keyed_map < T, enable_if_t < is_detected::value&& is_detected::value >> { + // maps with non-unique keys (std::multimap, std::unordered_multimap, ...) + // are excluded, because an object cannot hold duplicate keys; they are + // detected by insert() returning an iterator instead of a pair // NOLINTNEXTLINE(modernize-type-traits) we use C++11 - static constexpr bool value = std::is_enum::value; + static constexpr bool value = std::is_enum::value && + !(is_detected::value && !is_detected::value); }; template mm = {{TS_STOPPED, 1}, {TS_STOPPED, 2}}; + const std::unordered_multimap umm = {{TS_RUNNING, 3}, {TS_RUNNING, 3}}; + CHECK(json(mm) == json::parse(R"([["stopped",1],["stopped",2]])")); + CHECK(json(umm) == json::parse(R"([["running",3],["running",3]])")); + } + SECTION("keys that do not serialize to strings") { const std::map null_key = {{TS_INVALID, 1}};