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