From c5e63fd68430469f3734b0468b1db2e9d4fc5674 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= <theo.delrieu@tanker.io>
Date: Mon, 14 May 2018 11:51:37 +0200
Subject: [PATCH] Provide a from_json overload for std::map

This overload is chosen only when BasicJsonType::string_t
is not constructible from std::map::key_type.

Currently, converting a map to json treats it as an array of pairs.

fixes #1079
---
 .../nlohmann/detail/conversions/from_json.hpp | 20 ++++++++++++++++++
 single_include/nlohmann/json.hpp              | 20 ++++++++++++++++++
 test/src/unit-conversions.cpp                 | 21 +++++++++++++++++++
 3 files changed, 61 insertions(+)

diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp
index eccc04f1..ac4cea5f 100644
--- a/include/nlohmann/detail/conversions/from_json.hpp
+++ b/include/nlohmann/detail/conversions/from_json.hpp
@@ -5,6 +5,7 @@
 #include <ciso646> // and, not
 #include <forward_list> // forward_list
 #include <iterator> // inserter, front_inserter, end
+#include <map> // map
 #include <string> // string
 #include <tuple> // tuple, make_tuple
 #include <type_traits> // is_arithmetic, is_same, is_enum, underlying_type, is_convertible
@@ -277,6 +278,25 @@ void from_json(const BasicJsonType& j, std::tuple<Args...>& t)
     from_json_tuple_impl(j, t, index_sequence_for<Args...> {});
 }
 
+template <typename BasicJsonType, typename Key, typename Value,
+          typename = enable_if_t<not std::is_constructible<
+                                     typename BasicJsonType::string_t, Key>::value>>
+void from_json(const BasicJsonType& j, std::map<Key, Value>& m)
+{
+    if (JSON_UNLIKELY(not j.is_array()))
+    {
+        JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name())));
+    }
+    for (const auto& p : j)
+    {
+        if (JSON_UNLIKELY(not p.is_array()))
+        {
+            JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name())));
+        }
+        m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
+    }
+}
+
 struct from_json_fn
 {
   private:
diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp
index 8c9942bb..e3112f77 100644
--- a/single_include/nlohmann/json.hpp
+++ b/single_include/nlohmann/json.hpp
@@ -909,6 +909,7 @@ inline bool operator<(const value_t lhs, const value_t rhs) noexcept
 #include <ciso646> // and, not
 #include <forward_list> // forward_list
 #include <iterator> // inserter, front_inserter, end
+#include <map> // map
 #include <string> // string
 #include <tuple> // tuple, make_tuple
 #include <type_traits> // is_arithmetic, is_same, is_enum, underlying_type, is_convertible
@@ -1185,6 +1186,25 @@ void from_json(const BasicJsonType& j, std::tuple<Args...>& t)
     from_json_tuple_impl(j, t, index_sequence_for<Args...> {});
 }
 
+template <typename BasicJsonType, typename Key, typename Value,
+          typename = enable_if_t<not std::is_constructible<
+                                     typename BasicJsonType::string_t, Key>::value>>
+void from_json(const BasicJsonType& j, std::map<Key, Value>& m)
+{
+    if (JSON_UNLIKELY(not j.is_array()))
+    {
+        JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name())));
+    }
+    for (const auto& p : j)
+    {
+        if (JSON_UNLIKELY(not p.is_array()))
+        {
+            JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name())));
+        }
+        m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
+    }
+}
+
 struct from_json_fn
 {
   private:
diff --git a/test/src/unit-conversions.cpp b/test/src/unit-conversions.cpp
index 351dec8a..79ccf681 100644
--- a/test/src/unit-conversions.cpp
+++ b/test/src/unit-conversions.cpp
@@ -1079,6 +1079,26 @@ TEST_CASE("value conversion")
                 j5.get<std::unordered_set<std::string>>();
             }
 
+            SECTION("std::map (array of pairs)")
+            {
+                std::map<int, int> m{{0, 1}, {1, 2}, {2, 3}};
+                json j6 = m;
+
+                auto m2 = j6.get<std::map<int, int>>();
+                CHECK(m == m2);
+
+                json j7 = {0, 1, 2, 3};
+                CHECK_THROWS_AS((j7.get<std::map<int, int>>()), json::type_error&);
+                CHECK_THROWS_WITH((j7.get<std::map<int, int>>()), "[json.exception.type_error.302] type must be array, but is number");
+
+                SECTION("superfluous entries")
+                {
+                  json j8 = {{0, 1, 2}, {1, 2, 3}, {2, 3, 4}};
+                  m2 = j8.get<std::map<int, int>>();
+                  CHECK(m == m2);
+                }
+            }
+
             SECTION("exception in case of a non-object type")
             {
                 CHECK_THROWS_AS((json().get<std::list<int>>()), json::type_error&);
@@ -1094,6 +1114,7 @@ TEST_CASE("value conversion")
                 CHECK_THROWS_WITH((json().get<std::vector<json>>()), "[json.exception.type_error.302] type must be array, but is null");
                 CHECK_THROWS_WITH((json().get<std::list<json>>()), "[json.exception.type_error.302] type must be array, but is null");
                 CHECK_THROWS_WITH((json().get<std::valarray<int>>()), "[json.exception.type_error.302] type must be array, but is null");
+                CHECK_THROWS_WITH((json().get<std::map<int, int>>()), "[json.exception.type_error.302] type must be array, but is null");
             }
         }
     }