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}};