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