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