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_forward_declare crow::Request
45 
46 namespace redfish
47 {
48 
49 namespace json_util
50 {
51 
52 /**
53  * @brief Processes request to extract JSON from its body. If it fails, adds
54  *       MalformedJSON message to response and ends it.
55  *
56  * @param[io]  res       Response object
57  * @param[in]  req       Request object
58  * @param[out] reqJson   JSON object extracted from request's body
59  *
60  * @return true if JSON is valid, false when JSON is invalid and response has
61  *         been filled with message and ended.
62  */
63 bool processJsonFromRequest(crow::Response& res, const crow::Request& req,
64                             nlohmann::json& reqJson);
65 namespace details
66 {
67 
68 template <typename Type>
69 struct IsOptional : std::false_type
70 {};
71 
72 template <typename Type>
73 struct IsOptional<std::optional<Type>> : std::true_type
74 {};
75 
76 template <typename Type>
77 struct IsVector : std::false_type
78 {};
79 
80 template <typename Type>
81 struct IsVector<std::vector<Type>> : std::true_type
82 {};
83 
84 template <typename Type>
85 struct IsStdArray : std::false_type
86 {};
87 
88 template <typename Type, std::size_t size>
89 struct IsStdArray<std::array<Type, size>> : std::true_type
90 {};
91 
92 template <typename Type>
93 struct IsVariant : std::false_type
94 {};
95 
96 template <typename... Types>
97 struct IsVariant<std::variant<Types...>> : std::true_type
98 {};
99 
100 enum class UnpackErrorCode
101 {
102     success,
103     invalidType,
104     outOfRange
105 };
106 
107 template <typename ToType, typename FromType>
108 bool checkRange(const FromType& from [[maybe_unused]],
109                 std::string_view key [[maybe_unused]])
110 {
111     if constexpr (std::is_floating_point_v<ToType>)
112     {
113         if (std::isnan(from))
114         {
115             BMCWEB_LOG_DEBUG("Value for key {} was NAN", key);
116             return false;
117         }
118     }
119     if constexpr (std::numeric_limits<ToType>::max() <
120                   std::numeric_limits<FromType>::max())
121     {
122         if (from > std::numeric_limits<ToType>::max())
123         {
124             BMCWEB_LOG_DEBUG("Value for key {} was greater than max {}", key,
125                              std::numeric_limits<FromType>::max());
126             return false;
127         }
128     }
129     if constexpr (std::numeric_limits<ToType>::lowest() >
130                   std::numeric_limits<FromType>::lowest())
131     {
132         if (from < std::numeric_limits<ToType>::lowest())
133         {
134             BMCWEB_LOG_DEBUG("Value for key {} was less than min {}", key,
135                              std::numeric_limits<FromType>::lowest());
136             return false;
137         }
138     }
139 
140     return true;
141 }
142 
143 template <typename Type>
144 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue,
145                                          std::string_view key, Type& value);
146 
147 template <std::size_t Index = 0, typename... Args>
148 UnpackErrorCode unpackValueVariant(nlohmann::json& j, std::string_view key,
149                                    std::variant<Args...>& v)
150 {
151     if constexpr (Index < std::variant_size_v<std::variant<Args...>>)
152     {
153         std::variant_alternative_t<Index, std::variant<Args...>> type{};
154         UnpackErrorCode unpack = unpackValueWithErrorCode(j, key, type);
155         if (unpack == UnpackErrorCode::success)
156         {
157             v = std::move(type);
158             return unpack;
159         }
160 
161         return unpackValueVariant<Index + 1, Args...>(j, key, v);
162     }
163     return UnpackErrorCode::invalidType;
164 }
165 
166 template <typename Type>
167 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue,
168                                          std::string_view key, Type& value)
169 {
170     UnpackErrorCode ret = UnpackErrorCode::success;
171 
172     if constexpr (std::is_floating_point_v<Type>)
173     {
174         double helper = 0;
175         double* jsonPtr = jsonValue.get_ptr<double*>();
176 
177         if (jsonPtr == nullptr)
178         {
179             int64_t* intPtr = jsonValue.get_ptr<int64_t*>();
180             if (intPtr != nullptr)
181             {
182                 helper = static_cast<double>(*intPtr);
183                 jsonPtr = &helper;
184             }
185         }
186         if (jsonPtr == nullptr)
187         {
188             return UnpackErrorCode::invalidType;
189         }
190         if (!checkRange<Type>(*jsonPtr, key))
191         {
192             return UnpackErrorCode::outOfRange;
193         }
194         value = static_cast<Type>(*jsonPtr);
195     }
196 
197     else if constexpr (std::is_signed_v<Type>)
198     {
199         int64_t* jsonPtr = jsonValue.get_ptr<int64_t*>();
200         if (jsonPtr == nullptr)
201         {
202             return UnpackErrorCode::invalidType;
203         }
204         if (!checkRange<Type>(*jsonPtr, key))
205         {
206             return UnpackErrorCode::outOfRange;
207         }
208         value = static_cast<Type>(*jsonPtr);
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             return UnpackErrorCode::invalidType;
218         }
219         if (!checkRange<Type>(*jsonPtr, key))
220         {
221             return UnpackErrorCode::outOfRange;
222         }
223         value = static_cast<Type>(*jsonPtr);
224     }
225 
226     else if constexpr (std::is_same_v<nlohmann::json, Type>)
227     {
228         value = std::move(jsonValue);
229     }
230     else if constexpr (std::is_same_v<std::nullptr_t, Type>)
231     {
232         if (!jsonValue.is_null())
233         {
234             return UnpackErrorCode::invalidType;
235         }
236     }
237     else if constexpr (IsVector<Type>::value)
238     {
239         nlohmann::json::object_t* obj =
240             jsonValue.get_ptr<nlohmann::json::object_t*>();
241         if (obj == nullptr)
242         {
243             return UnpackErrorCode::invalidType;
244         }
245 
246         for (const auto& val : *obj)
247         {
248             value.emplace_back();
249             ret = unpackValueWithErrorCode<typename Type::value_type>(
250                       val, key, value.back()) &&
251                   ret;
252         }
253     }
254     else
255     {
256         using JsonType = std::add_const_t<std::add_pointer_t<Type>>;
257         JsonType jsonPtr = jsonValue.get_ptr<JsonType>();
258         if (jsonPtr == nullptr)
259         {
260             BMCWEB_LOG_DEBUG("Value for key {} was incorrect type: {}", key,
261                              jsonValue.type_name());
262             return UnpackErrorCode::invalidType;
263         }
264         value = std::move(*jsonPtr);
265     }
266     return ret;
267 }
268 
269 template <typename Type>
270 bool unpackValue(nlohmann::json& jsonValue, std::string_view key,
271                  crow::Response& res, Type& value)
272 {
273     bool ret = true;
274 
275     if constexpr (IsOptional<Type>::value)
276     {
277         value.emplace();
278         ret = unpackValue<typename Type::value_type>(jsonValue, key, res,
279                                                      *value) &&
280               ret;
281     }
282     else if constexpr (IsStdArray<Type>::value)
283     {
284         nlohmann::json::array_t* arr =
285             jsonValue.get_ptr<nlohmann::json::array_t*>();
286         if (arr == nullptr)
287         {
288             messages::propertyValueTypeError(res, res.jsonValue, key);
289             return false;
290         }
291         if (jsonValue.size() != value.size())
292         {
293             messages::propertyValueTypeError(res, res.jsonValue, key);
294             return false;
295         }
296         size_t index = 0;
297         for (auto& val : *arr)
298         {
299             ret = unpackValue<typename Type::value_type>(val, key, res,
300                                                          value[index++]) &&
301                   ret;
302         }
303     }
304     else if constexpr (IsVector<Type>::value)
305     {
306         nlohmann::json::array_t* arr =
307             jsonValue.get_ptr<nlohmann::json::array_t*>();
308         if (arr == nullptr)
309         {
310             messages::propertyValueTypeError(res, res.jsonValue, key);
311             return false;
312         }
313 
314         for (auto& val : *arr)
315         {
316             value.emplace_back();
317             ret = unpackValue<typename Type::value_type>(val, key, res,
318                                                          value.back()) &&
319                   ret;
320         }
321     }
322     else if constexpr (IsVariant<Type>::value)
323     {
324         UnpackErrorCode ec = unpackValueVariant(jsonValue, key, value);
325         if (ec != UnpackErrorCode::success)
326         {
327             if (ec == UnpackErrorCode::invalidType)
328             {
329                 messages::propertyValueTypeError(res, jsonValue, key);
330             }
331             else if (ec == UnpackErrorCode::outOfRange)
332             {
333                 messages::propertyValueNotInList(res, jsonValue, key);
334             }
335             return false;
336         }
337     }
338     else
339     {
340         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
341         if (ec != UnpackErrorCode::success)
342         {
343             if (ec == UnpackErrorCode::invalidType)
344             {
345                 messages::propertyValueTypeError(res, jsonValue, key);
346             }
347             else if (ec == UnpackErrorCode::outOfRange)
348             {
349                 messages::propertyValueNotInList(res, jsonValue, key);
350             }
351             return false;
352         }
353     }
354 
355     return ret;
356 }
357 
358 template <typename Type>
359 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, Type& value)
360 {
361     bool ret = true;
362     if constexpr (IsOptional<Type>::value)
363     {
364         value.emplace();
365         ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) &&
366               ret;
367     }
368     else if constexpr (IsStdArray<Type>::value)
369     {
370         nlohmann::json::array_t* arr =
371             jsonValue.get_ptr<nlohmann::json::array_t*>();
372         if (arr == nullptr)
373         {
374             return false;
375         }
376         if (jsonValue.size() != value.size())
377         {
378             return false;
379         }
380         size_t index = 0;
381         for (const auto& val : *arr)
382         {
383             ret = unpackValue<typename Type::value_type>(val, key,
384                                                          value[index++]) &&
385                   ret;
386         }
387     }
388     else if constexpr (IsVector<Type>::value)
389     {
390         nlohmann::json::array_t* arr =
391             jsonValue.get_ptr<nlohmann::json::array_t*>();
392         if (arr == nullptr)
393         {
394             return false;
395         }
396 
397         for (const auto& val : *arr)
398         {
399             value.emplace_back();
400             ret = unpackValue<typename Type::value_type>(val, key,
401                                                          value.back()) &&
402                   ret;
403         }
404     }
405     else
406     {
407         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
408         if (ec != UnpackErrorCode::success)
409         {
410             return false;
411         }
412     }
413 
414     return ret;
415 }
416 } // namespace details
417 
418 // clang-format off
419 using UnpackVariant = std::variant<
420     uint8_t*,
421     uint16_t*,
422     int16_t*,
423     uint32_t*,
424     int32_t*,
425     uint64_t*,
426     int64_t*,
427     bool*,
428     double*,
429     std::string*,
430     nlohmann::json::object_t*,
431     std::variant<std::string, std::nullptr_t>*,
432     std::variant<uint8_t, std::nullptr_t>*,
433     std::variant<int16_t, std::nullptr_t>*,
434     std::variant<uint16_t, std::nullptr_t>*,
435     std::variant<int32_t, std::nullptr_t>*,
436     std::variant<uint32_t, std::nullptr_t>*,
437     std::variant<int64_t, std::nullptr_t>*,
438     std::variant<uint64_t, std::nullptr_t>*,
439     std::variant<double, std::nullptr_t>*,
440     std::variant<bool, std::nullptr_t>*,
441     std::vector<uint8_t>*,
442     std::vector<uint16_t>*,
443     std::vector<int16_t>*,
444     std::vector<uint32_t>*,
445     std::vector<int32_t>*,
446     std::vector<uint64_t>*,
447     std::vector<int64_t>*,
448     //std::vector<bool>*,
449     std::vector<double>*,
450     std::vector<std::string>*,
451     std::vector<nlohmann::json::object_t>*,
452     std::optional<uint8_t>*,
453     std::optional<uint16_t>*,
454     std::optional<int16_t>*,
455     std::optional<uint32_t>*,
456     std::optional<int32_t>*,
457     std::optional<uint64_t>*,
458     std::optional<int64_t>*,
459     std::optional<bool>*,
460     std::optional<double>*,
461     std::optional<std::string>*,
462     std::optional<nlohmann::json::object_t>*,
463     std::optional<std::vector<uint8_t>>*,
464     std::optional<std::vector<uint16_t>>*,
465     std::optional<std::vector<int16_t>>*,
466     std::optional<std::vector<uint32_t>>*,
467     std::optional<std::vector<int32_t>>*,
468     std::optional<std::vector<uint64_t>>*,
469     std::optional<std::vector<int64_t>>*,
470     //std::optional<std::vector<bool>>*,
471     std::optional<std::vector<double>>*,
472     std::optional<std::vector<std::string>>*,
473     std::optional<std::vector<nlohmann::json::object_t>>*,
474     std::optional<std::variant<std::string, std::nullptr_t>>*,
475     std::optional<std::variant<uint8_t, std::nullptr_t>>*,
476     std::optional<std::variant<int16_t, std::nullptr_t>>*,
477     std::optional<std::variant<uint16_t, std::nullptr_t>>*,
478     std::optional<std::variant<int32_t, std::nullptr_t>>*,
479     std::optional<std::variant<uint32_t, std::nullptr_t>>*,
480     std::optional<std::variant<int64_t, std::nullptr_t>>*,
481     std::optional<std::variant<uint64_t, std::nullptr_t>>*,
482     std::optional<std::variant<double, std::nullptr_t>>*,
483     std::optional<std::variant<bool, std::nullptr_t>>*,
484     std::optional<std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>>*,
485     std::optional<std::vector<std::variant<std::string, nlohmann::json::object_t, std::nullptr_t>>>*,
486 
487     // Note, these types are kept for historical completeness, but should not be used,
488     // As they do not provide object type safety.  Instead, rely on nlohmann::json::object_t
489     // Will be removed Q2 2025
490     nlohmann::json*,
491     std::optional<std::vector<nlohmann::json>>*,
492     std::vector<nlohmann::json>*,
493     std::optional<nlohmann::json>*
494 >;
495 // clang-format on
496 
497 struct PerUnpack
498 {
499     std::string_view key;
500     UnpackVariant value;
501     bool complete = false;
502 };
503 
504 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
505                            std::span<PerUnpack> toUnpack);
506 
507 inline bool readJsonHelperObject(nlohmann::json::object_t& obj,
508                                  crow::Response& res,
509                                  std::span<PerUnpack> toUnpack)
510 {
511     bool result = true;
512     for (auto& item : obj)
513     {
514         size_t unpackIndex = 0;
515         for (; unpackIndex < toUnpack.size(); unpackIndex++)
516         {
517             PerUnpack& unpackSpec = toUnpack[unpackIndex];
518             std::string_view key = unpackSpec.key;
519             size_t keysplitIndex = key.find('/');
520             std::string_view leftover;
521             if (keysplitIndex != std::string_view::npos)
522             {
523                 leftover = key.substr(keysplitIndex + 1);
524                 key = key.substr(0, keysplitIndex);
525             }
526 
527             if (key != item.first || unpackSpec.complete)
528             {
529                 continue;
530             }
531 
532             // Sublevel key
533             if (!leftover.empty())
534             {
535                 // Include the slash in the key so we can compare later
536                 key = unpackSpec.key.substr(0, keysplitIndex + 1);
537                 nlohmann::json j;
538                 result = details::unpackValue<nlohmann::json>(item.second, key,
539                                                               res, j) &&
540                          result;
541                 if (!result)
542                 {
543                     return result;
544                 }
545 
546                 std::vector<PerUnpack> nextLevel;
547                 for (PerUnpack& p : toUnpack)
548                 {
549                     if (!p.key.starts_with(key))
550                     {
551                         continue;
552                     }
553                     std::string_view thisLeftover = p.key.substr(key.size());
554                     nextLevel.push_back({thisLeftover, p.value, false});
555                     p.complete = true;
556                 }
557 
558                 result = readJsonHelper(j, res, nextLevel) && result;
559                 break;
560             }
561 
562             result =
563                 std::visit(
564                     [&item, &unpackSpec, &res](auto& val) {
565                         using ContainedT =
566                             std::remove_pointer_t<std::decay_t<decltype(val)>>;
567                         return details::unpackValue<ContainedT>(
568                             item.second, unpackSpec.key, res, *val);
569                     },
570                     unpackSpec.value) &&
571                 result;
572 
573             unpackSpec.complete = true;
574             break;
575         }
576 
577         if (unpackIndex == toUnpack.size())
578         {
579             messages::propertyUnknown(res, item.first);
580             result = false;
581         }
582     }
583 
584     for (PerUnpack& perUnpack : toUnpack)
585     {
586         if (!perUnpack.complete)
587         {
588             bool isOptional = std::visit(
589                 [](auto& val) {
590                     using ContainedType =
591                         std::remove_pointer_t<std::decay_t<decltype(val)>>;
592                     return details::IsOptional<ContainedType>::value;
593                 },
594                 perUnpack.value);
595             if (isOptional)
596             {
597                 continue;
598             }
599             messages::propertyMissing(res, perUnpack.key);
600             result = false;
601         }
602     }
603     return result;
604 }
605 
606 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
607                            std::span<PerUnpack> toUnpack)
608 {
609     nlohmann::json::object_t* obj =
610         jsonRequest.get_ptr<nlohmann::json::object_t*>();
611     if (obj == nullptr)
612     {
613         BMCWEB_LOG_DEBUG("Json value is not an object");
614         messages::unrecognizedRequestBody(res);
615         return false;
616     }
617     return readJsonHelperObject(*obj, res, toUnpack);
618 }
619 
620 inline void packVariant(std::span<PerUnpack> /*toPack*/) {}
621 
622 template <typename FirstType, typename... UnpackTypes>
623 void packVariant(std::span<PerUnpack> toPack, std::string_view key,
624                  FirstType&& first, UnpackTypes&&... in)
625 {
626     if (toPack.empty())
627     {
628         return;
629     }
630     toPack[0].key = key;
631     toPack[0].value = &first;
632     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
633     packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...);
634 }
635 
636 template <typename FirstType, typename... UnpackTypes>
637 bool readJsonObject(nlohmann::json::object_t& jsonRequest, crow::Response& res,
638                     std::string_view key, FirstType&& first,
639                     UnpackTypes&&... in)
640 {
641     const std::size_t n = sizeof...(UnpackTypes) + 2;
642     std::array<PerUnpack, n / 2> toUnpack2;
643     packVariant(toUnpack2, key, std::forward<FirstType>(first),
644                 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