Make readJson accept object_t

Redfish supports several type systems for json.  This makes parsing into
proper types a challenge.  Nlohmann supports 3 core data types,
nlohmann::json, which supports all json types (float, int, array,
object).  Nlohmann::json::object_t, which is a specific typedef of
std::map, and nlohmann::json::array_t, which is a specific typedef of
std::map.

Redfish allows reading our arrays of complex objects, similar to

NtpServers: [null, {}, "string"]

Which makes it a challenge to support.  This commit allows parsing out
objects as a nlohmann::object_t, which gives the ability to later use it
in a type safe manner, without having to call
get_ptr<nlohmann::json::object_t later>.

Tested:
Unit tests pass.

Change-Id: I4134338951ce27c2f56841a45b56bc64ad1753db
Signed-off-by: Ed Tanous <ed@tanous.net>
diff --git a/redfish-core/include/utils/json_utils.hpp b/redfish-core/include/utils/json_utils.hpp
index 82f1fe2..c8af2d1 100644
--- a/redfish-core/include/utils/json_utils.hpp
+++ b/redfish-core/include/utils/json_utils.hpp
@@ -341,6 +341,7 @@
     double*,
     std::string*,
     nlohmann::json*,
+    nlohmann::json::object_t*,
     std::vector<uint8_t>*,
     std::vector<uint16_t>*,
     std::vector<int16_t>*,
@@ -352,6 +353,7 @@
     std::vector<double>*,
     std::vector<std::string>*,
     std::vector<nlohmann::json>*,
+    std::vector<nlohmann::json::object_t>*,
     std::optional<uint8_t>*,
     std::optional<uint16_t>*,
     std::optional<int16_t>*,
@@ -363,6 +365,7 @@
     std::optional<double>*,
     std::optional<std::string>*,
     std::optional<nlohmann::json>*,
+    std::optional<nlohmann::json::object_t>*,
     std::optional<std::vector<uint8_t>>*,
     std::optional<std::vector<uint16_t>>*,
     std::optional<std::vector<int16_t>>*,
@@ -373,7 +376,8 @@
     //std::optional<std::vector<bool>>*,
     std::optional<std::vector<double>>*,
     std::optional<std::vector<std::string>>*,
-    std::optional<std::vector<nlohmann::json>>*
+    std::optional<std::vector<nlohmann::json>>*,
+    std::optional<std::vector<nlohmann::json::object_t>>*
 >;
 // clang-format on
 
@@ -385,18 +389,14 @@
 };
 
 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
-                           std::span<PerUnpack> toUnpack)
+                           std::span<PerUnpack> toUnpack);
+
+inline bool readJsonHelperObject(nlohmann::json::object_t& obj,
+                                 crow::Response& res,
+                                 std::span<PerUnpack> toUnpack)
 {
     bool result = true;
-    nlohmann::json::object_t* obj =
-        jsonRequest.get_ptr<nlohmann::json::object_t*>();
-    if (obj == nullptr)
-    {
-        BMCWEB_LOG_DEBUG("Json value is not an object");
-        messages::unrecognizedRequestBody(res);
-        return false;
-    }
-    for (auto& item : *obj)
+    for (auto& item : obj)
     {
         size_t unpackIndex = 0;
         for (; unpackIndex < toUnpack.size(); unpackIndex++)
@@ -489,6 +489,20 @@
     return result;
 }
 
+inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
+                           std::span<PerUnpack> toUnpack)
+{
+    nlohmann::json::object_t* obj =
+        jsonRequest.get_ptr<nlohmann::json::object_t*>();
+    if (obj == nullptr)
+    {
+        BMCWEB_LOG_DEBUG("Json value is not an object");
+        messages::unrecognizedRequestBody(res);
+        return false;
+    }
+    return readJsonHelperObject(*obj, res, toUnpack);
+}
+
 inline void packVariant(std::span<PerUnpack> /*toPack*/) {}
 
 template <typename FirstType, typename... UnpackTypes>
@@ -506,16 +520,32 @@
 }
 
 template <typename FirstType, typename... UnpackTypes>
-bool readJson(nlohmann::json& jsonRequest, crow::Response& res,
-              std::string_view key, FirstType&& first, UnpackTypes&&... in)
+bool readJsonObject(nlohmann::json::object_t& jsonRequest, crow::Response& res,
+                    std::string_view key, FirstType&& first,
+                    UnpackTypes&&... in)
 {
     const std::size_t n = sizeof...(UnpackTypes) + 2;
     std::array<PerUnpack, n / 2> toUnpack2;
     packVariant(toUnpack2, key, first, std::forward<UnpackTypes&&>(in)...);
-    return readJsonHelper(jsonRequest, res, toUnpack2);
+    return readJsonHelperObject(jsonRequest, res, toUnpack2);
 }
 
-inline std::optional<nlohmann::json>
+template <typename FirstType, typename... UnpackTypes>
+bool readJson(nlohmann::json& jsonRequest, crow::Response& res,
+              std::string_view key, FirstType&& first, UnpackTypes&&... in)
+{
+    nlohmann::json::object_t* obj =
+        jsonRequest.get_ptr<nlohmann::json::object_t*>();
+    if (obj == nullptr)
+    {
+        BMCWEB_LOG_DEBUG("Json value is not an object");
+        messages::unrecognizedRequestBody(res);
+        return false;
+    }
+    return readJsonObject(*obj, res, key, first, in...);
+}
+
+inline std::optional<nlohmann::json::object_t>
     readJsonPatchHelper(const crow::Request& req, crow::Response& res)
 {
     nlohmann::json jsonRequest;
@@ -545,7 +575,7 @@
         return std::nullopt;
     }
 
-    return {std::move(jsonRequest)};
+    return {std::move(*object)};
 }
 
 template <typename... UnpackTypes>
@@ -557,8 +587,17 @@
     {
         return false;
     }
+    nlohmann::json::object_t* object =
+        jsonRequest->get_ptr<nlohmann::json::object_t*>();
+    if (object == nullptr)
+    {
+        BMCWEB_LOG_DEBUG("Json value is empty");
+        messages::emptyJSON(res);
+        return false;
+    }
 
-    return readJson(*jsonRequest, res, key, std::forward<UnpackTypes&&>(in)...);
+    return readJsonObject(*object, res, key,
+                          std::forward<UnpackTypes&&>(in)...);
 }
 
 template <typename... UnpackTypes>
@@ -571,7 +610,16 @@
         BMCWEB_LOG_DEBUG("Json value not readable");
         return false;
     }
-    return readJson(jsonRequest, res, key, std::forward<UnpackTypes&&>(in)...);
+    nlohmann::json::object_t* object =
+        jsonRequest.get_ptr<nlohmann::json::object_t*>();
+    if (object == nullptr)
+    {
+        BMCWEB_LOG_DEBUG("Json value is empty");
+        messages::emptyJSON(res);
+        return false;
+    }
+    return readJsonObject(*object, res, key,
+                          std::forward<UnpackTypes&&>(in)...);
 }
 
 // Determines if two json objects are less, based on the presence of the
diff --git a/test/redfish-core/include/utils/json_utils_test.cpp b/test/redfish-core/include/utils/json_utils_test.cpp
index 5485bba..ad4d805 100644
--- a/test/redfish-core/include/utils/json_utils_test.cpp
+++ b/test/redfish-core/include/utils/json_utils_test.cpp
@@ -48,6 +48,27 @@
     EXPECT_THAT(vec, ElementsAre(1, 2, 3));
 }
 
+TEST(ReadJson, ValidObjectElementsReturnsTrueResponseOkValuesUnpackedCorrectly)
+{
+    crow::Response res;
+    nlohmann::json::object_t jsonRequest;
+    jsonRequest["integer"] = 1;
+    jsonRequest["string"] = "hello";
+    jsonRequest["vector"] = std::vector<uint64_t>{1, 2, 3};
+
+    int64_t integer = 0;
+    std::string str;
+    std::vector<uint64_t> vec;
+    ASSERT_TRUE(readJsonObject(jsonRequest, res, "integer", integer, "string",
+                               str, "vector", vec));
+    EXPECT_EQ(res.result(), boost::beast::http::status::ok);
+    EXPECT_THAT(res.jsonValue, IsEmpty());
+
+    EXPECT_EQ(integer, 1);
+    EXPECT_EQ(str, "hello");
+    EXPECT_THAT(vec, ElementsAre(1, 2, 3));
+}
+
 TEST(readJson, ExtraElementsReturnsFalseReponseIsBadRequest)
 {
     crow::Response res;