xref: /openbmc/bmcweb/features/redfish/include/utils/json_utils.hpp (revision 8d9cf72d00ccbbdd070849601ce8f37bc01cb8c8)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
4 #pragma once
5 
6 #include "error_messages.hpp"
7 #include "http_request.hpp"
8 #include "http_response.hpp"
9 #include "human_sort.hpp"
10 #include "logging.hpp"
11 
12 #include <boost/system/result.hpp>
13 #include <boost/url/parse.hpp>
14 #include <boost/url/url_view.hpp>
15 #include <nlohmann/json.hpp>
16 
17 #include <algorithm>
18 #include <array>
19 #include <cmath>
20 #include <cstddef>
21 #include <cstdint>
22 #include <limits>
23 #include <map>
24 #include <optional>
25 #include <ranges>
26 #include <span>
27 #include <string>
28 #include <string_view>
29 #include <type_traits>
30 #include <utility>
31 #include <variant>
32 #include <vector>
33 
34 // IWYU pragma: no_forward_declare crow::Request
35 
36 namespace redfish
37 {
38 
39 namespace json_util
40 {
41 
42 /**
43  * @brief Processes request to extract JSON from its body. If it fails, adds
44  *       MalformedJSON message to response and ends it.
45  *
46  * @param[io]  res       Response object
47  * @param[in]  req       Request object
48  * @param[out] reqJson   JSON object extracted from request's body
49  *
50  * @return true if JSON is valid, false when JSON is invalid and response has
51  *         been filled with message and ended.
52  */
53 bool processJsonFromRequest(crow::Response& res, const crow::Request& req,
54                             nlohmann::json& reqJson);
55 namespace details
56 {
57 
58 template <typename Type>
59 struct IsOptional : std::false_type
60 {};
61 
62 template <typename Type>
63 struct IsOptional<std::optional<Type>> : std::true_type
64 {};
65 
66 template <typename Type>
67 struct IsVector : std::false_type
68 {};
69 
70 template <typename Type>
71 struct IsVector<std::vector<Type>> : std::true_type
72 {};
73 
74 template <typename Type>
75 struct IsStdArray : std::false_type
76 {};
77 
78 template <typename Type, std::size_t size>
79 struct IsStdArray<std::array<Type, size>> : std::true_type
80 {};
81 
82 template <typename Type>
83 struct IsVariant : std::false_type
84 {};
85 
86 template <typename... Types>
87 struct IsVariant<std::variant<Types...>> : std::true_type
88 {};
89 
90 enum class UnpackErrorCode
91 {
92     success,
93     invalidType,
94     outOfRange
95 };
96 
97 template <typename ToType, typename FromType>
98 bool checkRange(const FromType& from, std::string_view key)
99 {
100     if constexpr (std::is_floating_point_v<ToType>)
101     {
102         if (std::isnan(from))
103         {
104             BMCWEB_LOG_DEBUG("Value for key {} was NAN", key);
105             return false;
106         }
107         // Assume for the moment that all floats can represent the full range
108         // of any int/uint in a cast.  This is close enough to true for the
109         // precision of this json parser.
110     }
111     else
112     {
113         if (std::cmp_greater(from, std::numeric_limits<ToType>::max()))
114         {
115             BMCWEB_LOG_DEBUG("Value for key {} was greater than max {}", key,
116                              std::numeric_limits<FromType>::max());
117             return false;
118         }
119         if (std::cmp_less(from, std::numeric_limits<ToType>::lowest()))
120         {
121             BMCWEB_LOG_DEBUG("Value for key {} was less than min {}", key,
122                              std::numeric_limits<FromType>::lowest());
123             return false;
124         }
125     }
126 
127     return true;
128 }
129 
130 template <typename Type>
131 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue,
132                                          std::string_view key, Type& value);
133 
134 template <std::size_t Index = 0, typename... Args>
135 UnpackErrorCode unpackValueVariant(nlohmann::json& j, std::string_view key,
136                                    std::variant<Args...>& v)
137 {
138     if constexpr (Index < std::variant_size_v<std::variant<Args...>>)
139     {
140         std::variant_alternative_t<Index, std::variant<Args...>> type{};
141         UnpackErrorCode unpack = unpackValueWithErrorCode(j, key, type);
142         if (unpack == UnpackErrorCode::success)
143         {
144             v = std::move(type);
145             return unpack;
146         }
147 
148         return unpackValueVariant<Index + 1, Args...>(j, key, v);
149     }
150     return UnpackErrorCode::invalidType;
151 }
152 
153 template <typename Type>
154 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue,
155                                          std::string_view key, Type& value)
156 {
157     UnpackErrorCode ret = UnpackErrorCode::success;
158 
159     if constexpr (std::is_floating_point_v<Type>)
160     {
161         double helper = 0;
162         double* jsonPtr = jsonValue.get_ptr<double*>();
163 
164         if (jsonPtr == nullptr)
165         {
166             int64_t* intPtr = jsonValue.get_ptr<int64_t*>();
167             if (intPtr != nullptr)
168             {
169                 helper = static_cast<double>(*intPtr);
170                 jsonPtr = &helper;
171             }
172         }
173         if (jsonPtr == nullptr)
174         {
175             return UnpackErrorCode::invalidType;
176         }
177         if (!checkRange<Type>(*jsonPtr, key))
178         {
179             return UnpackErrorCode::outOfRange;
180         }
181         value = static_cast<Type>(*jsonPtr);
182     }
183 
184     else if constexpr (std::is_signed_v<Type>)
185     {
186         int64_t* jsonPtr = jsonValue.get_ptr<int64_t*>();
187         if (jsonPtr == nullptr)
188         {
189             // Value wasn't int, check uint
190             uint64_t* uJsonPtr = jsonValue.get_ptr<uint64_t*>();
191             if (uJsonPtr == nullptr)
192             {
193                 return UnpackErrorCode::invalidType;
194             }
195             if (!checkRange<Type>(*uJsonPtr, key))
196             {
197                 return UnpackErrorCode::outOfRange;
198             }
199             value = static_cast<Type>(*uJsonPtr);
200         }
201         else
202         {
203             if (!checkRange<Type>(*jsonPtr, key))
204             {
205                 return UnpackErrorCode::outOfRange;
206             }
207             value = static_cast<Type>(*jsonPtr);
208         }
209     }
210 
211     else if constexpr ((std::is_unsigned_v<Type>) &&
212                        (!std::is_same_v<bool, Type>))
213     {
214         uint64_t* jsonPtr = jsonValue.get_ptr<uint64_t*>();
215         if (jsonPtr == nullptr)
216         {
217             int64_t* ijsonPtr = jsonValue.get_ptr<int64_t*>();
218             if (ijsonPtr == nullptr)
219             {
220                 return UnpackErrorCode::invalidType;
221             }
222             if (!checkRange<Type>(*ijsonPtr, key))
223             {
224                 return UnpackErrorCode::outOfRange;
225             }
226             value = static_cast<Type>(*ijsonPtr);
227         }
228         else
229         {
230             if (!checkRange<Type>(*jsonPtr, key))
231             {
232                 return UnpackErrorCode::outOfRange;
233             }
234             value = static_cast<Type>(*jsonPtr);
235         }
236     }
237 
238     else if constexpr (std::is_same_v<nlohmann::json, Type>)
239     {
240         value = std::move(jsonValue);
241     }
242     else if constexpr (std::is_same_v<std::nullptr_t, Type>)
243     {
244         if (!jsonValue.is_null())
245         {
246             return UnpackErrorCode::invalidType;
247         }
248     }
249     else if constexpr (IsVector<Type>::value)
250     {
251         nlohmann::json::object_t* obj =
252             jsonValue.get_ptr<nlohmann::json::object_t*>();
253         if (obj == nullptr)
254         {
255             return UnpackErrorCode::invalidType;
256         }
257 
258         for (const auto& val : *obj)
259         {
260             value.emplace_back();
261             ret = unpackValueWithErrorCode<typename Type::value_type>(
262                       val, key, value.back()) &&
263                   ret;
264         }
265     }
266     else
267     {
268         using JsonType = std::add_const_t<std::add_pointer_t<Type>>;
269         JsonType jsonPtr = jsonValue.get_ptr<JsonType>();
270         if (jsonPtr == nullptr)
271         {
272             BMCWEB_LOG_DEBUG("Value for key {} was incorrect type: {}", key,
273                              jsonValue.type_name());
274             return UnpackErrorCode::invalidType;
275         }
276         value = std::move(*jsonPtr);
277     }
278     return ret;
279 }
280 
281 template <typename Type>
282 bool unpackValue(nlohmann::json& jsonValue, std::string_view key,
283                  crow::Response& res, Type& value)
284 {
285     bool ret = true;
286 
287     if constexpr (IsOptional<Type>::value)
288     {
289         value.emplace();
290         ret = unpackValue<typename Type::value_type>(jsonValue, key, res,
291                                                      *value) &&
292               ret;
293     }
294     else if constexpr (IsStdArray<Type>::value)
295     {
296         nlohmann::json::array_t* arr =
297             jsonValue.get_ptr<nlohmann::json::array_t*>();
298         if (arr == nullptr)
299         {
300             messages::propertyValueTypeError(res, jsonValue, key);
301             return false;
302         }
303         if (jsonValue.size() != value.size())
304         {
305             messages::propertyValueTypeError(res, jsonValue, key);
306             return false;
307         }
308         size_t index = 0;
309         for (auto& val : *arr)
310         {
311             ret = unpackValue<typename Type::value_type>(val, key, res,
312                                                          value[index++]) &&
313                   ret;
314         }
315     }
316     else if constexpr (IsVector<Type>::value)
317     {
318         nlohmann::json::array_t* arr =
319             jsonValue.get_ptr<nlohmann::json::array_t*>();
320         if (arr == nullptr)
321         {
322             messages::propertyValueTypeError(res, jsonValue, key);
323             return false;
324         }
325 
326         for (auto& val : *arr)
327         {
328             value.emplace_back();
329             ret = unpackValue<typename Type::value_type>(val, key, res,
330                                                          value.back()) &&
331                   ret;
332         }
333     }
334     else if constexpr (IsVariant<Type>::value)
335     {
336         UnpackErrorCode ec = unpackValueVariant(jsonValue, key, value);
337         if (ec != UnpackErrorCode::success)
338         {
339             if (ec == UnpackErrorCode::invalidType)
340             {
341                 messages::propertyValueTypeError(res, jsonValue, key);
342             }
343             else if (ec == UnpackErrorCode::outOfRange)
344             {
345                 messages::propertyValueOutOfRange(res, jsonValue, key);
346             }
347             return false;
348         }
349     }
350     else
351     {
352         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
353         if (ec != UnpackErrorCode::success)
354         {
355             if (ec == UnpackErrorCode::invalidType)
356             {
357                 messages::propertyValueTypeError(res, jsonValue, key);
358             }
359             else if (ec == UnpackErrorCode::outOfRange)
360             {
361                 messages::propertyValueOutOfRange(res, jsonValue, key);
362             }
363             return false;
364         }
365     }
366 
367     return ret;
368 }
369 
370 template <typename Type>
371 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, Type& value)
372 {
373     bool ret = true;
374     if constexpr (IsOptional<Type>::value)
375     {
376         value.emplace();
377         ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) &&
378               ret;
379     }
380     else if constexpr (IsStdArray<Type>::value)
381     {
382         nlohmann::json::array_t* arr =
383             jsonValue.get_ptr<nlohmann::json::array_t*>();
384         if (arr == nullptr)
385         {
386             return false;
387         }
388         if (jsonValue.size() != value.size())
389         {
390             return false;
391         }
392         size_t index = 0;
393         for (const auto& val : *arr)
394         {
395             ret = unpackValue<typename Type::value_type>(val, key,
396                                                          value[index++]) &&
397                   ret;
398         }
399     }
400     else if constexpr (IsVector<Type>::value)
401     {
402         nlohmann::json::array_t* arr =
403             jsonValue.get_ptr<nlohmann::json::array_t*>();
404         if (arr == nullptr)
405         {
406             return false;
407         }
408 
409         for (const auto& val : *arr)
410         {
411             value.emplace_back();
412             ret = unpackValue<typename Type::value_type>(val, key,
413                                                          value.back()) &&
414                   ret;
415         }
416     }
417     else
418     {
419         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
420         if (ec != UnpackErrorCode::success)
421         {
422             return false;
423         }
424     }
425 
426     return ret;
427 }
428 } // namespace details
429 
430 // clang-format off
431 using UnpackVariant = std::variant<
432     uint8_t*,
433     uint16_t*,
434     int16_t*,
435     uint32_t*,
436     int32_t*,
437     uint64_t*,
438     int64_t*,
439     bool*,
440     double*,
441     std::string*,
442     nlohmann::json::object_t*,
443     std::variant<std::string, std::nullptr_t>*,
444     std::variant<uint8_t, std::nullptr_t>*,
445     std::variant<int16_t, std::nullptr_t>*,
446     std::variant<uint16_t, std::nullptr_t>*,
447     std::variant<int32_t, std::nullptr_t>*,
448     std::variant<uint32_t, std::nullptr_t>*,
449     std::variant<int64_t, std::nullptr_t>*,
450     std::variant<uint64_t, std::nullptr_t>*,
451     std::variant<double, std::nullptr_t>*,
452     std::variant<bool, std::nullptr_t>*,
453     std::vector<uint8_t>*,
454     std::vector<uint16_t>*,
455     std::vector<int16_t>*,
456     std::vector<uint32_t>*,
457     std::vector<int32_t>*,
458     std::vector<uint64_t>*,
459     std::vector<int64_t>*,
460     //std::vector<bool>*,
461     std::vector<double>*,
462     std::vector<std::string>*,
463     std::vector<nlohmann::json::object_t>*,
464     std::optional<uint8_t>*,
465     std::optional<uint16_t>*,
466     std::optional<int16_t>*,
467     std::optional<uint32_t>*,
468     std::optional<int32_t>*,
469     std::optional<uint64_t>*,
470     std::optional<int64_t>*,
471     std::optional<bool>*,
472     std::optional<double>*,
473     std::optional<std::string>*,
474     std::optional<nlohmann::json::object_t>*,
475     std::optional<std::vector<uint8_t>>*,
476     std::optional<std::vector<uint16_t>>*,
477     std::optional<std::vector<int16_t>>*,
478     std::optional<std::vector<uint32_t>>*,
479     std::optional<std::vector<int32_t>>*,
480     std::optional<std::vector<uint64_t>>*,
481     std::optional<std::vector<int64_t>>*,
482     //std::optional<std::vector<bool>>*,
483     std::optional<std::vector<double>>*,
484     std::optional<std::vector<std::string>>*,
485     std::optional<std::vector<nlohmann::json::object_t>>*,
486     std::optional<std::variant<std::string, std::nullptr_t>>*,
487     std::optional<std::variant<uint8_t, std::nullptr_t>>*,
488     std::optional<std::variant<int16_t, std::nullptr_t>>*,
489     std::optional<std::variant<uint16_t, std::nullptr_t>>*,
490     std::optional<std::variant<int32_t, std::nullptr_t>>*,
491     std::optional<std::variant<uint32_t, std::nullptr_t>>*,
492     std::optional<std::variant<int64_t, std::nullptr_t>>*,
493     std::optional<std::variant<uint64_t, std::nullptr_t>>*,
494     std::optional<std::variant<double, std::nullptr_t>>*,
495     std::optional<std::variant<bool, std::nullptr_t>>*,
496     std::optional<std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>>*,
497     std::optional<std::vector<std::variant<std::string, nlohmann::json::object_t, std::nullptr_t>>>*,
498 
499     // Note, these types are kept for historical completeness, but should not be used,
500     // As they do not provide object type safety.  Instead, rely on nlohmann::json::object_t
501     // Will be removed Q2 2025
502     nlohmann::json*,
503     std::optional<std::vector<nlohmann::json>>*,
504     std::vector<nlohmann::json>*,
505     std::optional<nlohmann::json>*
506 >;
507 // clang-format on
508 
509 struct PerUnpack
510 {
511     std::string_view key;
512     UnpackVariant value;
513     bool complete = false;
514 };
515 
516 inline bool readJsonHelperObject(nlohmann::json::object_t& obj,
517                                  crow::Response& res,
518                                  std::span<PerUnpack> toUnpack)
519 {
520     bool result = true;
521     for (auto& item : obj)
522     {
523         size_t unpackIndex = 0;
524         for (; unpackIndex < toUnpack.size(); unpackIndex++)
525         {
526             PerUnpack& unpackSpec = toUnpack[unpackIndex];
527             std::string_view key = unpackSpec.key;
528             size_t keysplitIndex = key.find('/');
529             std::string_view leftover;
530             if (keysplitIndex != std::string_view::npos)
531             {
532                 leftover = key.substr(keysplitIndex + 1);
533                 key = key.substr(0, keysplitIndex);
534             }
535 
536             if (key != item.first || unpackSpec.complete)
537             {
538                 continue;
539             }
540 
541             // Sublevel key
542             if (!leftover.empty())
543             {
544                 // Include the slash in the key so we can compare later
545                 key = unpackSpec.key.substr(0, keysplitIndex + 1);
546                 nlohmann::json::object_t j;
547                 result = details::unpackValue<nlohmann::json::object_t>(
548                              item.second, key, res, j) &&
549                          result;
550                 if (!result)
551                 {
552                     return result;
553                 }
554 
555                 std::vector<PerUnpack> nextLevel;
556                 for (PerUnpack& p : toUnpack)
557                 {
558                     if (!p.key.starts_with(key))
559                     {
560                         continue;
561                     }
562                     std::string_view thisLeftover = p.key.substr(key.size());
563                     nextLevel.push_back({thisLeftover, p.value, false});
564                     p.complete = true;
565                 }
566 
567                 result = readJsonHelperObject(j, res, nextLevel) && result;
568                 break;
569             }
570 
571             result =
572                 std::visit(
573                     [&item, &unpackSpec, &res](auto& val) {
574                         using ContainedT =
575                             std::remove_pointer_t<std::decay_t<decltype(val)>>;
576                         return details::unpackValue<ContainedT>(
577                             item.second, unpackSpec.key, res, *val);
578                     },
579                     unpackSpec.value) &&
580                 result;
581 
582             unpackSpec.complete = true;
583             break;
584         }
585 
586         if (unpackIndex == toUnpack.size())
587         {
588             messages::propertyUnknown(res, item.first);
589             result = false;
590         }
591     }
592 
593     for (PerUnpack& perUnpack : toUnpack)
594     {
595         if (!perUnpack.complete)
596         {
597             bool isOptional = std::visit(
598                 [](auto& val) {
599                     using ContainedType =
600                         std::remove_pointer_t<std::decay_t<decltype(val)>>;
601                     return details::IsOptional<ContainedType>::value;
602                 },
603                 perUnpack.value);
604             if (isOptional)
605             {
606                 continue;
607             }
608             messages::propertyMissing(res, perUnpack.key);
609             result = false;
610         }
611     }
612     return result;
613 }
614 
615 inline void packVariant(std::span<PerUnpack> /*toPack*/) {}
616 
617 template <typename FirstType, typename... UnpackTypes>
618 void packVariant(std::span<PerUnpack> toPack, std::string_view key,
619                  FirstType&& first, UnpackTypes&&... in)
620 {
621     if (toPack.empty())
622     {
623         return;
624     }
625     toPack[0].key = key;
626     toPack[0].value = &first;
627     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
628     packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...);
629 }
630 
631 template <typename FirstType, typename... UnpackTypes>
632 bool readJsonObject(nlohmann::json::object_t& jsonRequest, crow::Response& res,
633                     std::string_view key, FirstType&& first,
634                     UnpackTypes&&... in)
635 {
636     const std::size_t n = sizeof...(UnpackTypes) + 2;
637     std::array<PerUnpack, n / 2> toUnpack2;
638     packVariant(toUnpack2, key, std::forward<FirstType>(first),
639                 std::forward<UnpackTypes&&>(in)...);
640     return readJsonHelperObject(jsonRequest, res, toUnpack2);
641 }
642 
643 template <typename FirstType, typename... UnpackTypes>
644 bool readJson(nlohmann::json& jsonRequest, crow::Response& res,
645               std::string_view key, FirstType&& first, UnpackTypes&&... in)
646 {
647     nlohmann::json::object_t* obj =
648         jsonRequest.get_ptr<nlohmann::json::object_t*>();
649     if (obj == nullptr)
650     {
651         BMCWEB_LOG_DEBUG("Json value is not an object");
652         messages::unrecognizedRequestBody(res);
653         return false;
654     }
655     return readJsonObject(*obj, res, key, std::forward<FirstType>(first),
656                           std::forward<UnpackTypes&&>(in)...);
657 }
658 
659 inline std::optional<nlohmann::json::object_t> readJsonPatchHelper(
660     const crow::Request& req, crow::Response& res)
661 {
662     nlohmann::json jsonRequest;
663     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
664     {
665         BMCWEB_LOG_DEBUG("Json value not readable");
666         return std::nullopt;
667     }
668     nlohmann::json::object_t* object =
669         jsonRequest.get_ptr<nlohmann::json::object_t*>();
670     if (object == nullptr || object->empty())
671     {
672         BMCWEB_LOG_DEBUG("Json value is empty");
673         messages::emptyJSON(res);
674         return std::nullopt;
675     }
676     std::erase_if(*object,
677                   [](const std::pair<std::string, nlohmann::json>& item) {
678                       return item.first.starts_with("@odata.");
679                   });
680     if (object->empty())
681     {
682         //  If the update request only contains OData annotations, the service
683         //  should return the HTTP 400 Bad Request status code with the
684         //  NoOperation message from the Base Message Registry, ...
685         messages::noOperation(res);
686         return std::nullopt;
687     }
688 
689     return {std::move(*object)};
690 }
691 
692 inline const nlohmann::json* findNestedKey(std::string_view key,
693                                            const nlohmann::json& value)
694 {
695     size_t keysplitIndex = key.find('/');
696     std::string_view leftover;
697     nlohmann::json::const_iterator it;
698     if (keysplitIndex != std::string_view::npos)
699     {
700         const nlohmann::json::object_t* obj =
701             value.get_ptr<const nlohmann::json::object_t*>();
702         if (obj == nullptr || obj->empty())
703         {
704             BMCWEB_LOG_ERROR("Requested key wasn't an object");
705             return nullptr;
706         }
707 
708         leftover = key.substr(keysplitIndex + 1);
709         std::string_view keypart = key.substr(0, keysplitIndex);
710         it = value.find(keypart);
711         if (it == value.end())
712         {
713             // Entry didn't have key
714             return nullptr;
715         }
716         return findNestedKey(leftover, it.value());
717     }
718 
719     it = value.find(key);
720     if (it == value.end())
721     {
722         return nullptr;
723     }
724     return &*it;
725 }
726 
727 template <typename... UnpackTypes>
728 bool readJsonPatch(const crow::Request& req, crow::Response& res,
729                    std::string_view key, UnpackTypes&&... in)
730 {
731     std::optional<nlohmann::json::object_t> jsonRequest =
732         readJsonPatchHelper(req, res);
733     if (!jsonRequest)
734     {
735         return false;
736     }
737     if (jsonRequest->empty())
738     {
739         messages::emptyJSON(res);
740         return false;
741     }
742 
743     return readJsonObject(*jsonRequest, res, key,
744                           std::forward<UnpackTypes&&>(in)...);
745 }
746 
747 inline std::optional<nlohmann::json::json_pointer>
748     createJsonPointerFromFragment(std::string_view input)
749 {
750     auto hashPos = input.find('#');
751     if (hashPos == std::string_view::npos || hashPos + 1 >= input.size())
752     {
753         BMCWEB_LOG_ERROR(
754             "createJsonPointerFromFragment() No fragment found after #");
755         return std::nullopt;
756     }
757 
758     std::string_view fragment = input.substr(hashPos + 1);
759     return nlohmann::json::json_pointer(std::string(fragment));
760 }
761 
762 template <typename... UnpackTypes>
763 bool readJsonAction(const crow::Request& req, crow::Response& res,
764                     const char* key, UnpackTypes&&... in)
765 {
766     nlohmann::json jsonRequest;
767     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
768     {
769         BMCWEB_LOG_DEBUG("Json value not readable");
770         return false;
771     }
772     nlohmann::json::object_t* object =
773         jsonRequest.get_ptr<nlohmann::json::object_t*>();
774     if (object == nullptr)
775     {
776         BMCWEB_LOG_DEBUG("Json value is empty");
777         messages::emptyJSON(res);
778         return false;
779     }
780     return readJsonObject(*object, res, key,
781                           std::forward<UnpackTypes&&>(in)...);
782 }
783 
784 // Determines if two json objects are less, based on the presence of the
785 // @odata.id key
786 inline int objectKeyCmp(std::string_view key, const nlohmann::json& a,
787                         const nlohmann::json& b)
788 {
789     using object_t = nlohmann::json::object_t;
790     const object_t* aObj = a.get_ptr<const object_t*>();
791     const object_t* bObj = b.get_ptr<const object_t*>();
792 
793     if (aObj == nullptr)
794     {
795         if (bObj == nullptr)
796         {
797             return 0;
798         }
799         return -1;
800     }
801     if (bObj == nullptr)
802     {
803         return 1;
804     }
805     object_t::const_iterator aIt = aObj->find(key);
806     object_t::const_iterator bIt = bObj->find(key);
807     // If either object doesn't have the key, they get "sorted" to the
808     // beginning.
809     if (aIt == aObj->end())
810     {
811         if (bIt == bObj->end())
812         {
813             return 0;
814         }
815         return -1;
816     }
817     if (bIt == bObj->end())
818     {
819         return 1;
820     }
821     const nlohmann::json::string_t* nameA =
822         aIt->second.get_ptr<const std::string*>();
823     const nlohmann::json::string_t* nameB =
824         bIt->second.get_ptr<const std::string*>();
825     // If either object doesn't have a string as the key, they get "sorted" to
826     // the beginning.
827     if (nameA == nullptr)
828     {
829         if (nameB == nullptr)
830         {
831             return 0;
832         }
833         return -1;
834     }
835     if (nameB == nullptr)
836     {
837         return 1;
838     }
839     if (key != "@odata.id")
840     {
841         return alphanumComp(*nameA, *nameB);
842     }
843 
844     boost::system::result<boost::urls::url_view> aUrl =
845         boost::urls::parse_relative_ref(*nameA);
846     boost::system::result<boost::urls::url_view> bUrl =
847         boost::urls::parse_relative_ref(*nameB);
848     if (!aUrl)
849     {
850         if (!bUrl)
851         {
852             return 0;
853         }
854         return -1;
855     }
856     if (!bUrl)
857     {
858         return 1;
859     }
860 
861     auto segmentsAIt = aUrl->segments().begin();
862     auto segmentsBIt = bUrl->segments().begin();
863 
864     while (true)
865     {
866         if (segmentsAIt == aUrl->segments().end())
867         {
868             if (segmentsBIt == bUrl->segments().end())
869             {
870                 return 0;
871             }
872             return -1;
873         }
874         if (segmentsBIt == bUrl->segments().end())
875         {
876             return 1;
877         }
878         int res = alphanumComp(*segmentsAIt, *segmentsBIt);
879         if (res != 0)
880         {
881             return res;
882         }
883 
884         segmentsAIt++;
885         segmentsBIt++;
886     }
887     return 0;
888 };
889 
890 // kept for backward compatibility
891 inline int odataObjectCmp(const nlohmann::json& left,
892                           const nlohmann::json& right)
893 {
894     return objectKeyCmp("@odata.id", left, right);
895 }
896 
897 struct ODataObjectLess
898 {
899     std::string_view key;
900 
901     explicit ODataObjectLess(std::string_view keyIn) : key(keyIn) {}
902 
903     bool operator()(const nlohmann::json& left,
904                     const nlohmann::json& right) const
905     {
906         return objectKeyCmp(key, left, right) < 0;
907     }
908 };
909 
910 // Sort the JSON array by |element[key]|.
911 // Elements without |key| or type of |element[key]| is not string are smaller
912 // those whose |element[key]| is string.
913 inline void sortJsonArrayByKey(nlohmann::json::array_t& array,
914                                std::string_view key)
915 {
916     std::ranges::sort(array, ODataObjectLess(key));
917 }
918 
919 // Sort the JSON array by |element[key]|.
920 // Elements without |key| or type of |element[key]| is not string are smaller
921 // those whose |element[key]| is string.
922 inline void sortJsonArrayByOData(nlohmann::json::array_t& array)
923 {
924     std::ranges::sort(array, ODataObjectLess("@odata.id"));
925 }
926 
927 // Returns the estimated size of the JSON value
928 // The implementation walks through every key and every value, accumulates the
929 //  total size of keys and values.
930 // Ideally, we should use a custom allocator that nlohmann JSON supports.
931 
932 // Assumption made:
933 //  1. number: 8 characters
934 //  2. boolean: 5 characters (False)
935 //  3. string: len(str) + 2 characters (quote)
936 //  4. bytes: len(bytes) characters
937 //  5. null: 4 characters (null)
938 uint64_t getEstimatedJsonSize(const nlohmann::json& root);
939 
940 } // namespace json_util
941 } // namespace redfish
942