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         if (!jsonValue.is_array())
286         {
287             messages::propertyValueTypeError(res, res.jsonValue, key);
288             return false;
289         }
290         if (jsonValue.size() != value.size())
291         {
292             messages::propertyValueTypeError(res, res.jsonValue, key);
293             return false;
294         }
295         size_t index = 0;
296         for (const auto& val : jsonValue.items())
297         {
298             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
299                                                          value[index++]) &&
300                   ret;
301         }
302     }
303     else if constexpr (IsVector<Type>::value)
304     {
305         if (!jsonValue.is_array())
306         {
307             messages::propertyValueTypeError(res, res.jsonValue, key);
308             return false;
309         }
310 
311         for (const auto& val : jsonValue.items())
312         {
313             value.emplace_back();
314             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
315                                                          value.back()) &&
316                   ret;
317         }
318     }
319     else if constexpr (IsVariant<Type>::value)
320     {
321         UnpackErrorCode ec = unpackValueVariant(jsonValue, key, value);
322         if (ec != UnpackErrorCode::success)
323         {
324             if (ec == UnpackErrorCode::invalidType)
325             {
326                 messages::propertyValueTypeError(res, jsonValue, key);
327             }
328             else if (ec == UnpackErrorCode::outOfRange)
329             {
330                 messages::propertyValueNotInList(res, jsonValue, key);
331             }
332             return false;
333         }
334     }
335     else
336     {
337         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
338         if (ec != UnpackErrorCode::success)
339         {
340             if (ec == UnpackErrorCode::invalidType)
341             {
342                 messages::propertyValueTypeError(res, jsonValue, key);
343             }
344             else if (ec == UnpackErrorCode::outOfRange)
345             {
346                 messages::propertyValueNotInList(res, jsonValue, key);
347             }
348             return false;
349         }
350     }
351 
352     return ret;
353 }
354 
355 template <typename Type>
356 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, Type& value)
357 {
358     bool ret = true;
359     if constexpr (IsOptional<Type>::value)
360     {
361         value.emplace();
362         ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) &&
363               ret;
364     }
365     else if constexpr (IsStdArray<Type>::value)
366     {
367         if (!jsonValue.is_array())
368         {
369             return false;
370         }
371         if (jsonValue.size() != value.size())
372         {
373             return false;
374         }
375         size_t index = 0;
376         for (const auto& val : jsonValue.items())
377         {
378             ret = unpackValue<typename Type::value_type>(val.value(), key,
379                                                          value[index++]) &&
380                   ret;
381         }
382     }
383     else if constexpr (IsVector<Type>::value)
384     {
385         if (!jsonValue.is_array())
386         {
387             return false;
388         }
389 
390         for (const auto& val : jsonValue.items())
391         {
392             value.emplace_back();
393             ret = unpackValue<typename Type::value_type>(val.value(), key,
394                                                          value.back()) &&
395                   ret;
396         }
397     }
398     else
399     {
400         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
401         if (ec != UnpackErrorCode::success)
402         {
403             return false;
404         }
405     }
406 
407     return ret;
408 }
409 } // namespace details
410 
411 // clang-format off
412 using UnpackVariant = std::variant<
413     uint8_t*,
414     uint16_t*,
415     int16_t*,
416     uint32_t*,
417     int32_t*,
418     uint64_t*,
419     int64_t*,
420     bool*,
421     double*,
422     std::string*,
423     nlohmann::json::object_t*,
424     std::variant<std::string, std::nullptr_t>*,
425     std::variant<uint8_t, std::nullptr_t>*,
426     std::variant<int16_t, std::nullptr_t>*,
427     std::variant<uint16_t, std::nullptr_t>*,
428     std::variant<int32_t, std::nullptr_t>*,
429     std::variant<uint32_t, std::nullptr_t>*,
430     std::variant<int64_t, std::nullptr_t>*,
431     std::variant<uint64_t, std::nullptr_t>*,
432     std::variant<double, std::nullptr_t>*,
433     std::variant<bool, std::nullptr_t>*,
434     std::vector<uint8_t>*,
435     std::vector<uint16_t>*,
436     std::vector<int16_t>*,
437     std::vector<uint32_t>*,
438     std::vector<int32_t>*,
439     std::vector<uint64_t>*,
440     std::vector<int64_t>*,
441     //std::vector<bool>*,
442     std::vector<double>*,
443     std::vector<std::string>*,
444     std::vector<nlohmann::json::object_t>*,
445     std::optional<uint8_t>*,
446     std::optional<uint16_t>*,
447     std::optional<int16_t>*,
448     std::optional<uint32_t>*,
449     std::optional<int32_t>*,
450     std::optional<uint64_t>*,
451     std::optional<int64_t>*,
452     std::optional<bool>*,
453     std::optional<double>*,
454     std::optional<std::string>*,
455     std::optional<nlohmann::json::object_t>*,
456     std::optional<std::vector<uint8_t>>*,
457     std::optional<std::vector<uint16_t>>*,
458     std::optional<std::vector<int16_t>>*,
459     std::optional<std::vector<uint32_t>>*,
460     std::optional<std::vector<int32_t>>*,
461     std::optional<std::vector<uint64_t>>*,
462     std::optional<std::vector<int64_t>>*,
463     //std::optional<std::vector<bool>>*,
464     std::optional<std::vector<double>>*,
465     std::optional<std::vector<std::string>>*,
466     std::optional<std::vector<nlohmann::json::object_t>>*,
467     std::optional<std::variant<std::string, std::nullptr_t>>*,
468     std::optional<std::variant<uint8_t, std::nullptr_t>>*,
469     std::optional<std::variant<int16_t, std::nullptr_t>>*,
470     std::optional<std::variant<uint16_t, std::nullptr_t>>*,
471     std::optional<std::variant<int32_t, std::nullptr_t>>*,
472     std::optional<std::variant<uint32_t, std::nullptr_t>>*,
473     std::optional<std::variant<int64_t, std::nullptr_t>>*,
474     std::optional<std::variant<uint64_t, std::nullptr_t>>*,
475     std::optional<std::variant<double, std::nullptr_t>>*,
476     std::optional<std::variant<bool, std::nullptr_t>>*,
477     std::optional<std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>>*,
478     std::optional<std::vector<std::variant<std::string, nlohmann::json::object_t, std::nullptr_t>>>*,
479 
480     // Note, these types are kept for historical completeness, but should not be used,
481     // As they do not provide object type safety.  Instead, rely on nlohmann::json::object_t
482     // Will be removed Q2 2025
483     nlohmann::json*,
484     std::optional<std::vector<nlohmann::json>>*,
485     std::vector<nlohmann::json>*,
486     std::optional<nlohmann::json>*
487 >;
488 // clang-format on
489 
490 struct PerUnpack
491 {
492     std::string_view key;
493     UnpackVariant value;
494     bool complete = false;
495 };
496 
497 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
498                            std::span<PerUnpack> toUnpack);
499 
500 inline bool readJsonHelperObject(nlohmann::json::object_t& obj,
501                                  crow::Response& res,
502                                  std::span<PerUnpack> toUnpack)
503 {
504     bool result = true;
505     for (auto& item : obj)
506     {
507         size_t unpackIndex = 0;
508         for (; unpackIndex < toUnpack.size(); unpackIndex++)
509         {
510             PerUnpack& unpackSpec = toUnpack[unpackIndex];
511             std::string_view key = unpackSpec.key;
512             size_t keysplitIndex = key.find('/');
513             std::string_view leftover;
514             if (keysplitIndex != std::string_view::npos)
515             {
516                 leftover = key.substr(keysplitIndex + 1);
517                 key = key.substr(0, keysplitIndex);
518             }
519 
520             if (key != item.first || unpackSpec.complete)
521             {
522                 continue;
523             }
524 
525             // Sublevel key
526             if (!leftover.empty())
527             {
528                 // Include the slash in the key so we can compare later
529                 key = unpackSpec.key.substr(0, keysplitIndex + 1);
530                 nlohmann::json j;
531                 result = details::unpackValue<nlohmann::json>(item.second, key,
532                                                               res, j) &&
533                          result;
534                 if (!result)
535                 {
536                     return result;
537                 }
538 
539                 std::vector<PerUnpack> nextLevel;
540                 for (PerUnpack& p : toUnpack)
541                 {
542                     if (!p.key.starts_with(key))
543                     {
544                         continue;
545                     }
546                     std::string_view thisLeftover = p.key.substr(key.size());
547                     nextLevel.push_back({thisLeftover, p.value, false});
548                     p.complete = true;
549                 }
550 
551                 result = readJsonHelper(j, res, nextLevel) && result;
552                 break;
553             }
554 
555             result = std::visit(
556                          [&item, &unpackSpec, &res](auto&& val) {
557                 using ContainedT =
558                     std::remove_pointer_t<std::decay_t<decltype(val)>>;
559                 return details::unpackValue<ContainedT>(
560                     item.second, unpackSpec.key, res, *val);
561             },
562                          unpackSpec.value) &&
563                      result;
564 
565             unpackSpec.complete = true;
566             break;
567         }
568 
569         if (unpackIndex == toUnpack.size())
570         {
571             messages::propertyUnknown(res, item.first);
572             result = false;
573         }
574     }
575 
576     for (PerUnpack& perUnpack : toUnpack)
577     {
578         if (!perUnpack.complete)
579         {
580             bool isOptional = std::visit(
581                 [](auto&& val) {
582                 using ContainedType =
583                     std::remove_pointer_t<std::decay_t<decltype(val)>>;
584                 return details::IsOptional<ContainedType>::value;
585             },
586                 perUnpack.value);
587             if (isOptional)
588             {
589                 continue;
590             }
591             messages::propertyMissing(res, perUnpack.key);
592             result = false;
593         }
594     }
595     return result;
596 }
597 
598 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
599                            std::span<PerUnpack> toUnpack)
600 {
601     nlohmann::json::object_t* obj =
602         jsonRequest.get_ptr<nlohmann::json::object_t*>();
603     if (obj == nullptr)
604     {
605         BMCWEB_LOG_DEBUG("Json value is not an object");
606         messages::unrecognizedRequestBody(res);
607         return false;
608     }
609     return readJsonHelperObject(*obj, res, toUnpack);
610 }
611 
612 inline void packVariant(std::span<PerUnpack> /*toPack*/) {}
613 
614 template <typename FirstType, typename... UnpackTypes>
615 void packVariant(std::span<PerUnpack> toPack, std::string_view key,
616                  FirstType& first, UnpackTypes&&... in)
617 {
618     if (toPack.empty())
619     {
620         return;
621     }
622     toPack[0].key = key;
623     toPack[0].value = &first;
624     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
625     packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...);
626 }
627 
628 template <typename FirstType, typename... UnpackTypes>
629 bool readJsonObject(nlohmann::json::object_t& jsonRequest, crow::Response& res,
630                     std::string_view key, FirstType&& first,
631                     UnpackTypes&&... in)
632 {
633     const std::size_t n = sizeof...(UnpackTypes) + 2;
634     std::array<PerUnpack, n / 2> toUnpack2;
635     packVariant(toUnpack2, key, first, 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 odataObjectCmp(const nlohmann::json& a, const nlohmann::json& b)
733 {
734     using object_t = nlohmann::json::object_t;
735     const object_t* aObj = a.get_ptr<const object_t*>();
736     const object_t* bObj = b.get_ptr<const object_t*>();
737 
738     if (aObj == nullptr)
739     {
740         if (bObj == nullptr)
741         {
742             return 0;
743         }
744         return -1;
745     }
746     if (bObj == nullptr)
747     {
748         return 1;
749     }
750     object_t::const_iterator aIt = aObj->find("@odata.id");
751     object_t::const_iterator bIt = bObj->find("@odata.id");
752     // If either object doesn't have the key, they get "sorted" to the end.
753     if (aIt == aObj->end())
754     {
755         if (bIt == bObj->end())
756         {
757             return 0;
758         }
759         return -1;
760     }
761     if (bIt == bObj->end())
762     {
763         return 1;
764     }
765     const nlohmann::json::string_t* nameA =
766         aIt->second.get_ptr<const std::string*>();
767     const nlohmann::json::string_t* nameB =
768         bIt->second.get_ptr<const std::string*>();
769     // If either object doesn't have a string as the key, they get "sorted" to
770     // the end.
771     if (nameA == nullptr)
772     {
773         if (nameB == nullptr)
774         {
775             return 0;
776         }
777         return -1;
778     }
779     if (nameB == nullptr)
780     {
781         return 1;
782     }
783     boost::urls::url_view aUrl(*nameA);
784     boost::urls::url_view bUrl(*nameB);
785     auto segmentsAIt = aUrl.segments().begin();
786     auto segmentsBIt = bUrl.segments().begin();
787 
788     while (true)
789     {
790         if (segmentsAIt == aUrl.segments().end())
791         {
792             if (segmentsBIt == bUrl.segments().end())
793             {
794                 return 0;
795             }
796             return -1;
797         }
798         if (segmentsBIt == bUrl.segments().end())
799         {
800             return 1;
801         }
802         int res = alphanumComp(*segmentsAIt, *segmentsBIt);
803         if (res != 0)
804         {
805             return res;
806         }
807 
808         segmentsAIt++;
809         segmentsBIt++;
810     }
811 };
812 
813 struct ODataObjectLess
814 {
815     bool operator()(const nlohmann::json& left,
816                     const nlohmann::json& right) const
817     {
818         return odataObjectCmp(left, right) < 0;
819     }
820 };
821 
822 // Sort the JSON array by |element[key]|.
823 // Elements without |key| or type of |element[key]| is not string are smaller
824 // those whose |element[key]| is string.
825 inline void sortJsonArrayByOData(nlohmann::json::array_t& array)
826 {
827     std::ranges::sort(array, ODataObjectLess());
828 }
829 
830 // Returns the estimated size of the JSON value
831 // The implementation walks through every key and every value, accumulates the
832 //  total size of keys and values.
833 // Ideally, we should use a custom allocator that nlohmann JSON supports.
834 
835 // Assumption made:
836 //  1. number: 8 characters
837 //  2. boolean: 5 characters (False)
838 //  3. string: len(str) + 2 characters (quote)
839 //  4. bytes: len(bytes) characters
840 //  5. null: 4 characters (null)
841 uint64_t getEstimatedJsonSize(const nlohmann::json& root);
842 
843 } // namespace json_util
844 } // namespace redfish
845