json utils: add getEstimatedJsonSize

Add a utility function which estimates the size of the JSON tree.

It is used in the children change to limit the reponse size of expand
query.

Tested:
1. unit test passed;
2. tested on hardware, the following are real sizes and estimation
```
Real payload size, Estimation, query
15.69 KB,  10.21 KB, redfish/v1?$expand=.($levels=1)
95.76 KB,  62.11 KB, redfish/v1?$expand=.($levels=2)
117.14 KB, 72.71 KB, redfish/v1?$expand=.($levels=3)
127.65 KB, 77.64 KB, redfish/v1?$expand=.($levels=4)
```

Signed-off-by: Nan Zhou <nanzhoumails@gmail.com>
Change-Id: Iae26d6732a6ec63ecc59eacf657b4bf33c07c046
diff --git a/redfish-core/include/utils/json_utils.hpp b/redfish-core/include/utils/json_utils.hpp
index 6e18157..642ca80 100644
--- a/redfish-core/include/utils/json_utils.hpp
+++ b/redfish-core/include/utils/json_utils.hpp
@@ -16,6 +16,7 @@
 #pragma once
 
 #include "error_messages.hpp"
+#include "http_connection.hpp"
 #include "http_request.hpp"
 #include "http_response.hpp"
 #include "human_sort.hpp"
@@ -673,5 +674,18 @@
     std::sort(array.begin(), array.end(), ODataObjectLess());
 }
 
+// Returns the estimated size of the JSON value
+// The implementation walks through every key and every value, accumulates the
+//  total size of keys and values.
+// Ideally, we should use a custom allocator that nlohmann JSON supports.
+
+// Assumption made:
+//  1. number: 8 characters
+//  2. boolean: 5 characters (False)
+//  3. string: len(str) + 2 characters (quote)
+//  4. bytes: len(bytes) characters
+//  5. null: 4 characters (null)
+uint64_t getEstimatedJsonSize(const nlohmann::json& root);
+
 } // namespace json_util
 } // namespace redfish
diff --git a/redfish-core/src/utils/json_utils.cpp b/redfish-core/src/utils/json_utils.cpp
index 5daf95b..5e44199 100644
--- a/redfish-core/src/utils/json_utils.cpp
+++ b/redfish-core/src/utils/json_utils.cpp
@@ -48,5 +48,54 @@
     return true;
 }
 
+uint64_t getEstimatedJsonSize(const nlohmann::json& root)
+{
+    if (root.is_null())
+    {
+        return 4;
+    }
+    if (root.is_number())
+    {
+        return 8;
+    }
+    if (root.is_boolean())
+    {
+        return 5;
+    }
+    if (root.is_string())
+    {
+        constexpr uint64_t quotesSize = 2;
+        return root.get<std::string>().size() + quotesSize;
+    }
+    if (root.is_binary())
+    {
+        return root.get_binary().size();
+    }
+    const nlohmann::json::array_t* arr =
+        root.get_ptr<const nlohmann::json::array_t*>();
+    if (arr != nullptr)
+    {
+        uint64_t sum = 0;
+        for (const auto& element : *arr)
+        {
+            sum += getEstimatedJsonSize(element);
+        }
+        return sum;
+    }
+    const nlohmann::json::object_t* object =
+        root.get_ptr<const nlohmann::json::object_t*>();
+    if (object != nullptr)
+    {
+        uint64_t sum = 0;
+        for (const auto& [k, v] : root.items())
+        {
+            constexpr uint64_t colonQuoteSpaceSize = 4;
+            sum += k.size() + getEstimatedJsonSize(v) + colonQuoteSpaceSize;
+        }
+        return sum;
+    }
+    return 0;
+}
+
 } // namespace json_util
 } // namespace redfish
diff --git a/test/redfish-core/include/utils/json_utils_test.cpp b/test/redfish-core/include/utils/json_utils_test.cpp
index 3fca4e7..5485bba 100644
--- a/test/redfish-core/include/utils/json_utils_test.cpp
+++ b/test/redfish-core/include/utils/json_utils_test.cpp
@@ -406,6 +406,60 @@
                             R"({"@odata.id" : "/redfish/v1/20"})"_json,
                             R"({"@odata.id" : "/redfish/v1/100"})"_json));
 }
+TEST(GetEstimatedJsonSize, NumberIs8Bytpes)
+{
+    EXPECT_EQ(getEstimatedJsonSize(nlohmann::json(123)), 8);
+    EXPECT_EQ(getEstimatedJsonSize(nlohmann::json(77777777777)), 8);
+    EXPECT_EQ(getEstimatedJsonSize(nlohmann::json(3.14)), 8);
+}
+
+TEST(GetEstimatedJsonSize, BooleanIs5Byte)
+{
+    EXPECT_EQ(getEstimatedJsonSize(nlohmann::json(true)), 5);
+    EXPECT_EQ(getEstimatedJsonSize(nlohmann::json(false)), 5);
+}
+
+TEST(GetEstimatedJsonSize, NullIs4Byte)
+{
+    EXPECT_EQ(getEstimatedJsonSize(nlohmann::json()), 4);
+}
+
+TEST(GetEstimatedJsonSize, StringAndBytesReturnsLengthAndQuote)
+{
+    EXPECT_EQ(getEstimatedJsonSize(nlohmann::json("1234")), 6);
+    EXPECT_EQ(getEstimatedJsonSize(nlohmann::json::binary({1, 2, 3, 4})), 4);
+}
+
+TEST(GetEstimatedJsonSize, ArrayReturnsSum)
+{
+    nlohmann::json arr = {1, 3.14, "123", nlohmann::json::binary({1, 2, 3, 4})};
+    EXPECT_EQ(getEstimatedJsonSize(arr), 8 + 8 + 5 + 4);
+}
+
+TEST(GetEstimatedJsonSize, ObjectsReturnsSumWithKeyAndValue)
+{
+    nlohmann::json obj = R"(
+{
+  "key0": 123,
+  "key1": "123",
+  "key2": [1, 2, 3],
+  "key3": {"key4": "123"}
+}
+)"_json;
+
+    uint64_t expected = 0;
+    // 5 keys of length 4
+    expected += uint64_t(5) * 4;
+    // 5 colons, 5 quote pairs, and 5 spaces for 5 keys
+    expected += uint64_t(5) * (1 + 2 + 1);
+    // 2 string values of length 3
+    expected += uint64_t(2) * (3 + 2);
+    // 1 number value
+    expected += 8;
+    // 1 array value of 3 numbers
+    expected += uint64_t(3) * 8;
+    EXPECT_EQ(getEstimatedJsonSize(obj), expected);
+}
 
 } // namespace
 } // namespace redfish::json_util