json utility: add sort
This commit adds a utility function |sortJsonArrayByKey|. It can sort an
json array by value of a given key of each element.
Use cases includes:
1. sort the MemberCollection by @odata.id
Tested:
1. unit test passed;
Signed-off-by: Nan Zhou <nanzhoumails@gmail.com>
Signed-off-by: Ed Tanous <edtanous@google.com>
Change-Id: Idc175fab3af5c6102a5a3439b712b659ecb76468
diff --git a/redfish-core/include/utils/json_utils.hpp b/redfish-core/include/utils/json_utils.hpp
index b98cf0e..5ecce0a 100644
--- a/redfish-core/include/utils/json_utils.hpp
+++ b/redfish-core/include/utils/json_utils.hpp
@@ -18,10 +18,12 @@
#include "error_messages.hpp"
#include "http_request.hpp"
#include "http_response.hpp"
+#include "human_sort.hpp"
#include "logging.hpp"
#include <nlohmann/json.hpp>
+#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
@@ -590,5 +592,106 @@
}
return readJson(jsonRequest, res, key, std::forward<UnpackTypes&&>(in)...);
}
+
+// Determines if two json objects are less, based on the presence of the
+// @odata.id key
+inline int odataObjectCmp(const nlohmann::json& a, const nlohmann::json& b)
+{
+ using object_t = nlohmann::json::object_t;
+ const object_t* aObj = a.get_ptr<const object_t*>();
+ const object_t* bObj = b.get_ptr<const object_t*>();
+
+ if (aObj == nullptr)
+ {
+ if (bObj == nullptr)
+ {
+ return 0;
+ }
+ return -1;
+ }
+ if (bObj == nullptr)
+ {
+ return 1;
+ }
+ object_t::const_iterator aIt = aObj->find("@odata.id");
+ object_t::const_iterator bIt = bObj->find("@odata.id");
+ // If either object doesn't have the key, they get "sorted" to the end.
+ if (aIt == aObj->end())
+ {
+ if (bIt == bObj->end())
+ {
+ return 0;
+ }
+ return -1;
+ }
+ if (bIt == bObj->end())
+ {
+ return 1;
+ }
+ const nlohmann::json::string_t* nameA =
+ aIt->second.get_ptr<const std::string*>();
+ const nlohmann::json::string_t* nameB =
+ bIt->second.get_ptr<const std::string*>();
+ // If either object doesn't have a string as the key, they get "sorted" to
+ // the end.
+ if (nameA == nullptr)
+ {
+ if (nameB == nullptr)
+ {
+ return 0;
+ }
+ return -1;
+ }
+ if (nameB == nullptr)
+ {
+ return 1;
+ }
+ boost::urls::url_view aUrl(*nameA);
+ boost::urls::url_view bUrl(*nameB);
+ auto segmentsAIt = aUrl.segments().begin();
+ auto segmentsBIt = bUrl.segments().begin();
+
+ while (true)
+ {
+ if (segmentsAIt == aUrl.segments().end())
+ {
+ if (segmentsBIt == bUrl.segments().end())
+ {
+ return 0;
+ }
+ return -1;
+ }
+ if (segmentsBIt == bUrl.segments().end())
+ {
+ return 1;
+ }
+ int res = alphanumComp(*segmentsAIt, *segmentsBIt);
+ if (res != 0)
+ {
+ return res;
+ }
+
+ segmentsAIt++;
+ segmentsBIt++;
+ }
+};
+
+struct ODataObjectLess
+{
+ bool operator()(const nlohmann::json& left,
+ const nlohmann::json& right) const
+ {
+ return odataObjectCmp(left, right) < 0;
+ }
+};
+
+// Sort the JSON array by |element[key]|.
+// Elements without |key| or type of |element[key]| is not string are smaller
+// those whose |element[key]| is string.
+inline void sortJsonArrayByOData(nlohmann::json::array_t& array)
+{
+ std::sort(array.begin(), array.end(), ODataObjectLess());
+}
+
} // 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 fa5e39a..3fca4e7 100644
--- a/test/redfish-core/include/utils/json_utils_test.cpp
+++ b/test/redfish-core/include/utils/json_utils_test.cpp
@@ -354,5 +354,58 @@
EXPECT_THAT(res.jsonValue, IsEmpty());
}
+TEST(odataObjectCmp, PositiveCases)
+{
+ EXPECT_EQ(0, odataObjectCmp(R"({"@odata.id": "/redfish/v1/1"})"_json,
+ R"({"@odata.id": "/redfish/v1/1"})"_json));
+ EXPECT_EQ(0, odataObjectCmp(R"({"@odata.id": ""})"_json,
+ R"({"@odata.id": ""})"_json));
+ EXPECT_EQ(0, odataObjectCmp(R"({"@odata.id": 42})"_json,
+ R"({"@odata.id": 0})"_json));
+ EXPECT_EQ(0, odataObjectCmp(R"({})"_json, R"({})"_json));
+
+ EXPECT_GT(0, odataObjectCmp(R"({"@odata.id": "/redfish/v1"})"_json,
+ R"({"@odata.id": "/redfish/v1/1"})"_json));
+ EXPECT_LT(0, odataObjectCmp(R"({"@odata.id": "/redfish/v1/1"})"_json,
+ R"({"@odata.id": "/redfish/v1"})"_json));
+
+ EXPECT_LT(0, odataObjectCmp(R"({"@odata.id": "/10"})"_json,
+ R"({"@odata.id": "/1"})"_json));
+ EXPECT_GT(0, odataObjectCmp(R"({"@odata.id": "/1"})"_json,
+ R"({"@odata.id": "/10"})"_json));
+
+ EXPECT_GT(0, odataObjectCmp(R"({})"_json, R"({"@odata.id": "/1"})"_json));
+ EXPECT_LT(0, odataObjectCmp(R"({"@odata.id": "/1"})"_json, R"({})"_json));
+
+ EXPECT_GT(0, odataObjectCmp(R"({"@odata.id": 4})"_json,
+ R"({"@odata.id": "/1"})"_json));
+ EXPECT_LT(0, odataObjectCmp(R"({"@odata.id": "/1"})"_json,
+ R"({"@odata.id": 4})"_json));
+}
+
+TEST(SortJsonArrayByKey, ElementMissingKeyReturnsFalseArrayIsPartlySorted)
+{
+ nlohmann::json::array_t array =
+ R"([{"@odata.id" : "/redfish/v1/100"}, {"@odata.id": "/redfish/v1/1"}, {"@odata.id" : "/redfish/v1/20"}])"_json;
+ sortJsonArrayByOData(array);
+ // Objects with other keys are always larger than those with the specified
+ // key.
+ EXPECT_THAT(array,
+ ElementsAre(R"({"@odata.id": "/redfish/v1/1"})"_json,
+ R"({"@odata.id" : "/redfish/v1/20"})"_json,
+ R"({"@odata.id" : "/redfish/v1/100"})"_json));
+}
+
+TEST(SortJsonArrayByKey, SortedByStringValueOnSuccessArrayIsSorted)
+{
+ nlohmann::json::array_t array =
+ R"([{"@odata.id": "/redfish/v1/20"}, {"@odata.id" : "/redfish/v1"}, {"@odata.id" : "/redfish/v1/100"}])"_json;
+ sortJsonArrayByOData(array);
+ EXPECT_THAT(array,
+ ElementsAre(R"({"@odata.id": "/redfish/v1"})"_json,
+ R"({"@odata.id" : "/redfish/v1/20"})"_json,
+ R"({"@odata.id" : "/redfish/v1/100"})"_json));
+}
+
} // namespace
} // namespace redfish::json_util