xref: /openbmc/bmcweb/features/redfish/include/utils/json_utils.hpp (revision 08fad5d9dc59323a8916ff97a035221621047d8c)
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 
429 //  boost::hash_combine
430 inline std::size_t combine(std::size_t seed, std::size_t h) noexcept
431 {
432     seed ^= h + 0x9e3779b9 + (seed << 6U) + (seed >> 2U);
433     return seed;
434 }
435 } // namespace details
436 
437 // clang-format off
438 using UnpackVariant = std::variant<
439     uint8_t*,
440     uint16_t*,
441     int16_t*,
442     uint32_t*,
443     int32_t*,
444     uint64_t*,
445     int64_t*,
446     bool*,
447     double*,
448     std::string*,
449     nlohmann::json::object_t*,
450     std::variant<std::string, std::nullptr_t>*,
451     std::variant<uint8_t, std::nullptr_t>*,
452     std::variant<int16_t, std::nullptr_t>*,
453     std::variant<uint16_t, std::nullptr_t>*,
454     std::variant<int32_t, std::nullptr_t>*,
455     std::variant<uint32_t, std::nullptr_t>*,
456     std::variant<int64_t, std::nullptr_t>*,
457     std::variant<uint64_t, std::nullptr_t>*,
458     std::variant<double, std::nullptr_t>*,
459     std::variant<bool, std::nullptr_t>*,
460     std::vector<uint8_t>*,
461     std::vector<uint16_t>*,
462     std::vector<int16_t>*,
463     std::vector<uint32_t>*,
464     std::vector<int32_t>*,
465     std::vector<uint64_t>*,
466     std::vector<int64_t>*,
467     //std::vector<bool>*,
468     std::vector<double>*,
469     std::vector<std::string>*,
470     std::vector<nlohmann::json::object_t>*,
471     std::optional<uint8_t>*,
472     std::optional<uint16_t>*,
473     std::optional<int16_t>*,
474     std::optional<uint32_t>*,
475     std::optional<int32_t>*,
476     std::optional<uint64_t>*,
477     std::optional<int64_t>*,
478     std::optional<bool>*,
479     std::optional<double>*,
480     std::optional<std::string>*,
481     std::optional<nlohmann::json::object_t>*,
482     std::optional<std::vector<uint8_t>>*,
483     std::optional<std::vector<uint16_t>>*,
484     std::optional<std::vector<int16_t>>*,
485     std::optional<std::vector<uint32_t>>*,
486     std::optional<std::vector<int32_t>>*,
487     std::optional<std::vector<uint64_t>>*,
488     std::optional<std::vector<int64_t>>*,
489     //std::optional<std::vector<bool>>*,
490     std::optional<std::vector<double>>*,
491     std::optional<std::vector<std::string>>*,
492     std::optional<std::vector<nlohmann::json::object_t>>*,
493     std::optional<std::variant<std::string, std::nullptr_t>>*,
494     std::optional<std::variant<uint8_t, std::nullptr_t>>*,
495     std::optional<std::variant<int16_t, std::nullptr_t>>*,
496     std::optional<std::variant<uint16_t, std::nullptr_t>>*,
497     std::optional<std::variant<int32_t, std::nullptr_t>>*,
498     std::optional<std::variant<uint32_t, std::nullptr_t>>*,
499     std::optional<std::variant<int64_t, std::nullptr_t>>*,
500     std::optional<std::variant<uint64_t, std::nullptr_t>>*,
501     std::optional<std::variant<double, std::nullptr_t>>*,
502     std::optional<std::variant<bool, std::nullptr_t>>*,
503     std::optional<std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>>*,
504     std::optional<std::vector<std::variant<std::string, nlohmann::json::object_t, std::nullptr_t>>>*,
505 
506     // Note, these types are kept for historical completeness, but should not be used,
507     // As they do not provide object type safety.  Instead, rely on nlohmann::json::object_t
508     // Will be removed Q2 2025
509     nlohmann::json*,
510     std::optional<std::vector<nlohmann::json>>*,
511     std::vector<nlohmann::json>*,
512     std::optional<nlohmann::json>*
513 >;
514 // clang-format on
515 
516 struct PerUnpack
517 {
518     std::string_view key;
519     UnpackVariant value;
520     bool complete = false;
521 };
522 
523 inline bool readJsonHelperObject(nlohmann::json::object_t& obj,
524                                  crow::Response& res,
525                                  std::span<PerUnpack> toUnpack)
526 {
527     bool result = true;
528     for (auto& item : obj)
529     {
530         size_t unpackIndex = 0;
531         for (; unpackIndex < toUnpack.size(); unpackIndex++)
532         {
533             PerUnpack& unpackSpec = toUnpack[unpackIndex];
534             std::string_view key = unpackSpec.key;
535             size_t keysplitIndex = key.find('/');
536             std::string_view leftover;
537             if (keysplitIndex != std::string_view::npos)
538             {
539                 leftover = key.substr(keysplitIndex + 1);
540                 key = key.substr(0, keysplitIndex);
541             }
542 
543             if (key != item.first || unpackSpec.complete)
544             {
545                 continue;
546             }
547 
548             // Sublevel key
549             if (!leftover.empty())
550             {
551                 // Include the slash in the key so we can compare later
552                 key = unpackSpec.key.substr(0, keysplitIndex + 1);
553                 nlohmann::json::object_t j;
554                 result = details::unpackValue<nlohmann::json::object_t>(
555                              item.second, key, res, j) &&
556                          result;
557                 if (!result)
558                 {
559                     return result;
560                 }
561 
562                 std::vector<PerUnpack> nextLevel;
563                 for (PerUnpack& p : toUnpack)
564                 {
565                     if (!p.key.starts_with(key))
566                     {
567                         continue;
568                     }
569                     std::string_view thisLeftover = p.key.substr(key.size());
570                     nextLevel.push_back({thisLeftover, p.value, false});
571                     p.complete = true;
572                 }
573 
574                 result = readJsonHelperObject(j, res, nextLevel) && result;
575                 break;
576             }
577 
578             result =
579                 std::visit(
580                     [&item, &unpackSpec, &res](auto& val) {
581                         using ContainedT =
582                             std::remove_pointer_t<std::decay_t<decltype(val)>>;
583                         return details::unpackValue<ContainedT>(
584                             item.second, unpackSpec.key, res, *val);
585                     },
586                     unpackSpec.value) &&
587                 result;
588 
589             unpackSpec.complete = true;
590             break;
591         }
592 
593         if (unpackIndex == toUnpack.size())
594         {
595             messages::propertyUnknown(res, item.first);
596             result = false;
597         }
598     }
599 
600     for (PerUnpack& perUnpack : toUnpack)
601     {
602         if (!perUnpack.complete)
603         {
604             bool isOptional = std::visit(
605                 [](auto& val) {
606                     using ContainedType =
607                         std::remove_pointer_t<std::decay_t<decltype(val)>>;
608                     return details::IsOptional<ContainedType>::value;
609                 },
610                 perUnpack.value);
611             if (isOptional)
612             {
613                 continue;
614             }
615             messages::propertyMissing(res, perUnpack.key);
616             result = false;
617         }
618     }
619     return result;
620 }
621 
622 inline void packVariant(std::span<PerUnpack> /*toPack*/) {}
623 
624 template <typename FirstType, typename... UnpackTypes>
625 void packVariant(std::span<PerUnpack> toPack, std::string_view key,
626                  FirstType&& first, UnpackTypes&&... in)
627 {
628     if (toPack.empty())
629     {
630         return;
631     }
632     toPack[0].key = key;
633     toPack[0].value = &first;
634     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
635     packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...);
636 }
637 
638 template <typename FirstType, typename... UnpackTypes>
639 bool readJsonObject(nlohmann::json::object_t& jsonRequest, crow::Response& res,
640                     std::string_view key, FirstType&& first,
641                     UnpackTypes&&... in)
642 {
643     const std::size_t n = sizeof...(UnpackTypes) + 2;
644     std::array<PerUnpack, n / 2> toUnpack2;
645     packVariant(toUnpack2, key, std::forward<FirstType>(first),
646                 std::forward<UnpackTypes&&>(in)...);
647     return readJsonHelperObject(jsonRequest, res, toUnpack2);
648 }
649 
650 template <typename FirstType, typename... UnpackTypes>
651 bool readJson(nlohmann::json& jsonRequest, crow::Response& res,
652               std::string_view key, FirstType&& first, UnpackTypes&&... in)
653 {
654     nlohmann::json::object_t* obj =
655         jsonRequest.get_ptr<nlohmann::json::object_t*>();
656     if (obj == nullptr)
657     {
658         BMCWEB_LOG_DEBUG("Json value is not an object");
659         messages::unrecognizedRequestBody(res);
660         return false;
661     }
662     return readJsonObject(*obj, res, key, std::forward<FirstType>(first),
663                           std::forward<UnpackTypes&&>(in)...);
664 }
665 
666 inline std::optional<nlohmann::json::object_t> readJsonPatchHelper(
667     const crow::Request& req, crow::Response& res)
668 {
669     nlohmann::json jsonRequest;
670     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
671     {
672         BMCWEB_LOG_DEBUG("Json value not readable");
673         return std::nullopt;
674     }
675     nlohmann::json::object_t* object =
676         jsonRequest.get_ptr<nlohmann::json::object_t*>();
677     if (object == nullptr || object->empty())
678     {
679         BMCWEB_LOG_DEBUG("Json value is empty");
680         messages::emptyJSON(res);
681         return std::nullopt;
682     }
683     std::erase_if(*object,
684                   [](const std::pair<std::string, nlohmann::json>& item) {
685                       return item.first.starts_with("@odata.");
686                   });
687     if (object->empty())
688     {
689         //  If the update request only contains OData annotations, the service
690         //  should return the HTTP 400 Bad Request status code with the
691         //  NoOperation message from the Base Message Registry, ...
692         messages::noOperation(res);
693         return std::nullopt;
694     }
695 
696     return {std::move(*object)};
697 }
698 
699 inline const nlohmann::json* findNestedKey(std::string_view key,
700                                            const nlohmann::json& value)
701 {
702     size_t keysplitIndex = key.find('/');
703     std::string_view leftover;
704     nlohmann::json::const_iterator it;
705     if (keysplitIndex != std::string_view::npos)
706     {
707         const nlohmann::json::object_t* obj =
708             value.get_ptr<const nlohmann::json::object_t*>();
709         if (obj == nullptr || obj->empty())
710         {
711             BMCWEB_LOG_ERROR("Requested key wasn't an object");
712             return nullptr;
713         }
714 
715         leftover = key.substr(keysplitIndex + 1);
716         std::string_view keypart = key.substr(0, keysplitIndex);
717         it = value.find(keypart);
718         if (it == value.end())
719         {
720             // Entry didn't have key
721             return nullptr;
722         }
723         return findNestedKey(leftover, it.value());
724     }
725 
726     it = value.find(key);
727     if (it == value.end())
728     {
729         return nullptr;
730     }
731     return &*it;
732 }
733 
734 template <typename... UnpackTypes>
735 bool readJsonPatch(const crow::Request& req, crow::Response& res,
736                    std::string_view key, UnpackTypes&&... in)
737 {
738     std::optional<nlohmann::json::object_t> jsonRequest =
739         readJsonPatchHelper(req, res);
740     if (!jsonRequest)
741     {
742         return false;
743     }
744     if (jsonRequest->empty())
745     {
746         messages::emptyJSON(res);
747         return false;
748     }
749 
750     return readJsonObject(*jsonRequest, res, key,
751                           std::forward<UnpackTypes&&>(in)...);
752 }
753 
754 inline std::optional<nlohmann::json::json_pointer>
755     createJsonPointerFromFragment(std::string_view input)
756 {
757     auto hashPos = input.find('#');
758     if (hashPos == std::string_view::npos || hashPos + 1 >= input.size())
759     {
760         BMCWEB_LOG_ERROR(
761             "createJsonPointerFromFragment() No fragment found after #");
762         return std::nullopt;
763     }
764 
765     std::string_view fragment = input.substr(hashPos + 1);
766     return nlohmann::json::json_pointer(std::string(fragment));
767 }
768 
769 template <typename... UnpackTypes>
770 bool readJsonAction(const crow::Request& req, crow::Response& res,
771                     const char* key, UnpackTypes&&... in)
772 {
773     nlohmann::json jsonRequest;
774     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
775     {
776         BMCWEB_LOG_DEBUG("Json value not readable");
777         return false;
778     }
779     nlohmann::json::object_t* object =
780         jsonRequest.get_ptr<nlohmann::json::object_t*>();
781     if (object == nullptr)
782     {
783         BMCWEB_LOG_DEBUG("Json value is empty");
784         messages::emptyJSON(res);
785         return false;
786     }
787     return readJsonObject(*object, res, key,
788                           std::forward<UnpackTypes&&>(in)...);
789 }
790 
791 // Determines if two json objects are less, based on the presence of the
792 // @odata.id key
793 inline int objectKeyCmp(std::string_view key, const nlohmann::json& a,
794                         const nlohmann::json& b)
795 {
796     using object_t = nlohmann::json::object_t;
797     const object_t* aObj = a.get_ptr<const object_t*>();
798     const object_t* bObj = b.get_ptr<const object_t*>();
799 
800     if (aObj == nullptr)
801     {
802         if (bObj == nullptr)
803         {
804             return 0;
805         }
806         return -1;
807     }
808     if (bObj == nullptr)
809     {
810         return 1;
811     }
812     object_t::const_iterator aIt = aObj->find(key);
813     object_t::const_iterator bIt = bObj->find(key);
814     // If either object doesn't have the key, they get "sorted" to the
815     // beginning.
816     if (aIt == aObj->end())
817     {
818         if (bIt == bObj->end())
819         {
820             return 0;
821         }
822         return -1;
823     }
824     if (bIt == bObj->end())
825     {
826         return 1;
827     }
828     const nlohmann::json::string_t* nameA =
829         aIt->second.get_ptr<const std::string*>();
830     const nlohmann::json::string_t* nameB =
831         bIt->second.get_ptr<const std::string*>();
832     // If either object doesn't have a string as the key, they get "sorted" to
833     // the beginning.
834     if (nameA == nullptr)
835     {
836         if (nameB == nullptr)
837         {
838             return 0;
839         }
840         return -1;
841     }
842     if (nameB == nullptr)
843     {
844         return 1;
845     }
846     if (key != "@odata.id")
847     {
848         return alphanumComp(*nameA, *nameB);
849     }
850 
851     boost::system::result<boost::urls::url_view> aUrl =
852         boost::urls::parse_relative_ref(*nameA);
853     boost::system::result<boost::urls::url_view> bUrl =
854         boost::urls::parse_relative_ref(*nameB);
855     if (!aUrl)
856     {
857         if (!bUrl)
858         {
859             return 0;
860         }
861         return -1;
862     }
863     if (!bUrl)
864     {
865         return 1;
866     }
867 
868     auto segmentsAIt = aUrl->segments().begin();
869     auto segmentsBIt = bUrl->segments().begin();
870 
871     while (true)
872     {
873         if (segmentsAIt == aUrl->segments().end())
874         {
875             if (segmentsBIt == bUrl->segments().end())
876             {
877                 return 0;
878             }
879             return -1;
880         }
881         if (segmentsBIt == bUrl->segments().end())
882         {
883             return 1;
884         }
885         int res = alphanumComp(*segmentsAIt, *segmentsBIt);
886         if (res != 0)
887         {
888             return res;
889         }
890 
891         segmentsAIt++;
892         segmentsBIt++;
893     }
894     return 0;
895 };
896 
897 // kept for backward compatibility
898 inline int odataObjectCmp(const nlohmann::json& left,
899                           const nlohmann::json& right)
900 {
901     return objectKeyCmp("@odata.id", left, right);
902 }
903 
904 struct ODataObjectLess
905 {
906     std::string_view key;
907 
908     explicit ODataObjectLess(std::string_view keyIn) : key(keyIn) {}
909 
910     bool operator()(const nlohmann::json& left,
911                     const nlohmann::json& right) const
912     {
913         return objectKeyCmp(key, left, right) < 0;
914     }
915 };
916 
917 // Sort the JSON array by |element[key]|.
918 // Elements without |key| or type of |element[key]| is not string are smaller
919 // those whose |element[key]| is string.
920 inline void sortJsonArrayByKey(nlohmann::json::array_t& array,
921                                std::string_view key)
922 {
923     std::ranges::sort(array, ODataObjectLess(key));
924 }
925 
926 // Sort the JSON array by |element[key]|.
927 // Elements without |key| or type of |element[key]| is not string are smaller
928 // those whose |element[key]| is string.
929 inline void sortJsonArrayByOData(nlohmann::json::array_t& array)
930 {
931     std::ranges::sort(array, ODataObjectLess("@odata.id"));
932 }
933 
934 // Returns the estimated size of the JSON value
935 // The implementation walks through every key and every value, accumulates the
936 //  total size of keys and values.
937 // Ideally, we should use a custom allocator that nlohmann JSON supports.
938 
939 // Assumption made:
940 //  1. number: 8 characters
941 //  2. boolean: 5 characters (False)
942 //  3. string: len(str) + 2 characters (quote)
943 //  4. bytes: len(bytes) characters
944 //  5. null: 4 characters (null)
945 uint64_t getEstimatedJsonSize(const nlohmann::json& root);
946 
947 // Hashes a json value, recursively omitting every member with key `keyToIgnore`
948 inline size_t hashJsonWithoutKey(const nlohmann::json& jsonValue,
949                                  std::string_view keyToIgnore)
950 {
951     const nlohmann::json::object_t* obj =
952         jsonValue.get_ptr<const nlohmann::json::object_t*>();
953     if (obj == nullptr)
954     {
955         // Object has no keys to remove so just return hash
956         return std::hash<nlohmann::json>{}(jsonValue);
957     }
958 
959     const size_t type = static_cast<std::size_t>(jsonValue.type());
960     size_t seed = details::combine(type, jsonValue.size());
961     for (const auto& element : *obj)
962     {
963         const size_t h = std::hash<std::string>{}(element.first);
964         seed = details::combine(seed, h);
965         if (element.first != keyToIgnore)
966         {
967             seed = details::combine(
968                 seed, std::hash<nlohmann::json>{}(element.second));
969         }
970     }
971     return seed;
972 }
973 
974 } // namespace json_util
975 } // namespace redfish
976