xref: /openbmc/bmcweb/redfish-core/include/utils/json_utils.hpp (revision 76b038f20fed54ff61450a175160dc0af21b3cf9)
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, std::string_view key)
110 {
111     if (from > std::numeric_limits<ToType>::max())
112     {
113         BMCWEB_LOG_DEBUG("Value for key {} was greater than max: {}", key,
114                          __PRETTY_FUNCTION__);
115         return false;
116     }
117     if (from < std::numeric_limits<ToType>::lowest())
118     {
119         BMCWEB_LOG_DEBUG("Value for key {} was less than min: {}", key,
120                          __PRETTY_FUNCTION__);
121         return false;
122     }
123     if constexpr (std::is_floating_point_v<ToType>)
124     {
125         if (std::isnan(from))
126         {
127             BMCWEB_LOG_DEBUG("Value for key {} was NAN", key);
128             return false;
129         }
130     }
131 
132     return true;
133 }
134 
135 template <typename Type>
136 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue,
137                                          std::string_view key, Type& value);
138 
139 template <std::size_t Index = 0, typename... Args>
140 UnpackErrorCode unpackValueVariant(nlohmann::json& j, std::string_view key,
141                                    std::variant<Args...>& v)
142 {
143     if constexpr (Index < std::variant_size_v<std::variant<Args...>>)
144     {
145         std::variant_alternative_t<Index, std::variant<Args...>> type;
146         UnpackErrorCode unpack = unpackValueWithErrorCode(j, key, type);
147         if (unpack == UnpackErrorCode::success)
148         {
149             v = std::move(type);
150             return unpack;
151         }
152 
153         return unpackValueVariant<Index + 1, Args...>(j, key, v);
154     }
155     return UnpackErrorCode::invalidType;
156 }
157 
158 template <typename Type>
159 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue,
160                                          std::string_view key, Type& value)
161 {
162     UnpackErrorCode ret = UnpackErrorCode::success;
163 
164     if constexpr (std::is_floating_point_v<Type>)
165     {
166         double helper = 0;
167         double* jsonPtr = jsonValue.get_ptr<double*>();
168 
169         if (jsonPtr == nullptr)
170         {
171             int64_t* intPtr = jsonValue.get_ptr<int64_t*>();
172             if (intPtr != nullptr)
173             {
174                 helper = static_cast<double>(*intPtr);
175                 jsonPtr = &helper;
176             }
177         }
178         if (jsonPtr == nullptr)
179         {
180             return UnpackErrorCode::invalidType;
181         }
182         if (!checkRange<Type>(*jsonPtr, key))
183         {
184             return UnpackErrorCode::outOfRange;
185         }
186         value = static_cast<Type>(*jsonPtr);
187     }
188 
189     else if constexpr (std::is_signed_v<Type>)
190     {
191         int64_t* jsonPtr = jsonValue.get_ptr<int64_t*>();
192         if (jsonPtr == nullptr)
193         {
194             return UnpackErrorCode::invalidType;
195         }
196         if (!checkRange<Type>(*jsonPtr, key))
197         {
198             return UnpackErrorCode::outOfRange;
199         }
200         value = static_cast<Type>(*jsonPtr);
201     }
202 
203     else if constexpr ((std::is_unsigned_v<Type>)&&(
204                            !std::is_same_v<bool, Type>))
205     {
206         uint64_t* jsonPtr = jsonValue.get_ptr<uint64_t*>();
207         if (jsonPtr == nullptr)
208         {
209             return UnpackErrorCode::invalidType;
210         }
211         if (!checkRange<Type>(*jsonPtr, key))
212         {
213             return UnpackErrorCode::outOfRange;
214         }
215         value = static_cast<Type>(*jsonPtr);
216     }
217 
218     else if constexpr (std::is_same_v<nlohmann::json, Type>)
219     {
220         value = std::move(jsonValue);
221     }
222     else if constexpr (std::is_same_v<std::nullptr_t, Type>)
223     {
224         if (!jsonValue.is_null())
225         {
226             return UnpackErrorCode::invalidType;
227         }
228     }
229     else
230     {
231         using JsonType = std::add_const_t<std::add_pointer_t<Type>>;
232         JsonType jsonPtr = jsonValue.get_ptr<JsonType>();
233         if (jsonPtr == nullptr)
234         {
235             BMCWEB_LOG_DEBUG("Value for key {} was incorrect type: {}", key,
236                              jsonValue.type_name());
237             return UnpackErrorCode::invalidType;
238         }
239         value = std::move(*jsonPtr);
240     }
241     return ret;
242 }
243 
244 template <typename Type>
245 bool unpackValue(nlohmann::json& jsonValue, std::string_view key,
246                  crow::Response& res, Type& value)
247 {
248     bool ret = true;
249 
250     if constexpr (IsOptional<Type>::value)
251     {
252         value.emplace();
253         ret = unpackValue<typename Type::value_type>(jsonValue, key, res,
254                                                      *value) &&
255               ret;
256     }
257     else if constexpr (IsStdArray<Type>::value)
258     {
259         if (!jsonValue.is_array())
260         {
261             messages::propertyValueTypeError(res, res.jsonValue, key);
262             return false;
263         }
264         if (jsonValue.size() != value.size())
265         {
266             messages::propertyValueTypeError(res, res.jsonValue, key);
267             return false;
268         }
269         size_t index = 0;
270         for (const auto& val : jsonValue.items())
271         {
272             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
273                                                          value[index++]) &&
274                   ret;
275         }
276     }
277     else if constexpr (IsVector<Type>::value)
278     {
279         if (!jsonValue.is_array())
280         {
281             messages::propertyValueTypeError(res, res.jsonValue, key);
282             return false;
283         }
284 
285         for (const auto& val : jsonValue.items())
286         {
287             value.emplace_back();
288             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
289                                                          value.back()) &&
290                   ret;
291         }
292     }
293     else if constexpr (IsVariant<Type>::value)
294     {
295         UnpackErrorCode ec = unpackValueVariant(jsonValue, key, value);
296         if (ec != UnpackErrorCode::success)
297         {
298             if (ec == UnpackErrorCode::invalidType)
299             {
300                 messages::propertyValueTypeError(res, jsonValue, key);
301             }
302             else if (ec == UnpackErrorCode::outOfRange)
303             {
304                 messages::propertyValueNotInList(res, jsonValue, key);
305             }
306             return false;
307         }
308     }
309     else
310     {
311         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
312         if (ec != UnpackErrorCode::success)
313         {
314             if (ec == UnpackErrorCode::invalidType)
315             {
316                 messages::propertyValueTypeError(res, jsonValue, key);
317             }
318             else if (ec == UnpackErrorCode::outOfRange)
319             {
320                 messages::propertyValueNotInList(res, jsonValue, key);
321             }
322             return false;
323         }
324     }
325 
326     return ret;
327 }
328 
329 template <typename Type>
330 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, Type& value)
331 {
332     bool ret = true;
333     if constexpr (IsOptional<Type>::value)
334     {
335         value.emplace();
336         ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) &&
337               ret;
338     }
339     else if constexpr (IsStdArray<Type>::value)
340     {
341         if (!jsonValue.is_array())
342         {
343             return false;
344         }
345         if (jsonValue.size() != value.size())
346         {
347             return false;
348         }
349         size_t index = 0;
350         for (const auto& val : jsonValue.items())
351         {
352             ret = unpackValue<typename Type::value_type>(val.value(), key,
353                                                          value[index++]) &&
354                   ret;
355         }
356     }
357     else if constexpr (IsVector<Type>::value)
358     {
359         if (!jsonValue.is_array())
360         {
361             return false;
362         }
363 
364         for (const auto& val : jsonValue.items())
365         {
366             value.emplace_back();
367             ret = unpackValue<typename Type::value_type>(val.value(), key,
368                                                          value.back()) &&
369                   ret;
370         }
371     }
372     else
373     {
374         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
375         if (ec != UnpackErrorCode::success)
376         {
377             return false;
378         }
379     }
380 
381     return ret;
382 }
383 } // namespace details
384 
385 // clang-format off
386 using UnpackVariant = std::variant<
387     uint8_t*,
388     uint16_t*,
389     int16_t*,
390     uint32_t*,
391     int32_t*,
392     uint64_t*,
393     int64_t*,
394     bool*,
395     double*,
396     std::string*,
397     nlohmann::json*,
398     nlohmann::json::object_t*,
399     std::variant<std::string, std::nullptr_t>*,
400     std::variant<uint8_t, std::nullptr_t>*,
401     std::variant<int16_t, std::nullptr_t>*,
402     std::variant<uint16_t, std::nullptr_t>*,
403     std::variant<int32_t, std::nullptr_t>*,
404     std::variant<uint32_t, std::nullptr_t>*,
405     std::variant<int64_t, std::nullptr_t>*,
406     std::variant<uint64_t, std::nullptr_t>*,
407     std::variant<double, std::nullptr_t>*,
408     std::variant<bool, std::nullptr_t>*,
409     std::vector<uint8_t>*,
410     std::vector<uint16_t>*,
411     std::vector<int16_t>*,
412     std::vector<uint32_t>*,
413     std::vector<int32_t>*,
414     std::vector<uint64_t>*,
415     std::vector<int64_t>*,
416     //std::vector<bool>*,
417     std::vector<double>*,
418     std::vector<std::string>*,
419     std::vector<nlohmann::json>*,
420     std::vector<nlohmann::json::object_t>*,
421     std::optional<uint8_t>*,
422     std::optional<uint16_t>*,
423     std::optional<int16_t>*,
424     std::optional<uint32_t>*,
425     std::optional<int32_t>*,
426     std::optional<uint64_t>*,
427     std::optional<int64_t>*,
428     std::optional<bool>*,
429     std::optional<double>*,
430     std::optional<std::string>*,
431     std::optional<nlohmann::json>*,
432     std::optional<nlohmann::json::object_t>*,
433     std::optional<std::vector<uint8_t>>*,
434     std::optional<std::vector<uint16_t>>*,
435     std::optional<std::vector<int16_t>>*,
436     std::optional<std::vector<uint32_t>>*,
437     std::optional<std::vector<int32_t>>*,
438     std::optional<std::vector<uint64_t>>*,
439     std::optional<std::vector<int64_t>>*,
440     //std::optional<std::vector<bool>>*,
441     std::optional<std::vector<double>>*,
442     std::optional<std::vector<std::string>>*,
443     std::optional<std::vector<nlohmann::json>>*,
444     std::optional<std::vector<nlohmann::json::object_t>>*,
445     std::optional<std::variant<std::string, std::nullptr_t>>*,
446     std::optional<std::variant<uint8_t, std::nullptr_t>>*,
447     std::optional<std::variant<int16_t, std::nullptr_t>>*,
448     std::optional<std::variant<uint16_t, std::nullptr_t>>*,
449     std::optional<std::variant<int32_t, std::nullptr_t>>*,
450     std::optional<std::variant<uint32_t, std::nullptr_t>>*,
451     std::optional<std::variant<int64_t, std::nullptr_t>>*,
452     std::optional<std::variant<uint64_t, std::nullptr_t>>*,
453     std::optional<std::variant<double, std::nullptr_t>>*,
454     std::optional<std::variant<bool, std::nullptr_t>>*,
455     std::optional<std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>>*,
456     std::optional<std::variant<nlohmann::json::object_t, std::nullptr_t>>*
457 >;
458 // clang-format on
459 
460 struct PerUnpack
461 {
462     std::string_view key;
463     UnpackVariant value;
464     bool complete = false;
465 };
466 
467 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
468                            std::span<PerUnpack> toUnpack);
469 
470 inline bool readJsonHelperObject(nlohmann::json::object_t& obj,
471                                  crow::Response& res,
472                                  std::span<PerUnpack> toUnpack)
473 {
474     bool result = true;
475     for (auto& item : obj)
476     {
477         size_t unpackIndex = 0;
478         for (; unpackIndex < toUnpack.size(); unpackIndex++)
479         {
480             PerUnpack& unpackSpec = toUnpack[unpackIndex];
481             std::string_view key = unpackSpec.key;
482             size_t keysplitIndex = key.find('/');
483             std::string_view leftover;
484             if (keysplitIndex != std::string_view::npos)
485             {
486                 leftover = key.substr(keysplitIndex + 1);
487                 key = key.substr(0, keysplitIndex);
488             }
489 
490             if (key != item.first || unpackSpec.complete)
491             {
492                 continue;
493             }
494 
495             // Sublevel key
496             if (!leftover.empty())
497             {
498                 // Include the slash in the key so we can compare later
499                 key = unpackSpec.key.substr(0, keysplitIndex + 1);
500                 nlohmann::json j;
501                 result = details::unpackValue<nlohmann::json>(item.second, key,
502                                                               res, j) &&
503                          result;
504                 if (!result)
505                 {
506                     return result;
507                 }
508 
509                 std::vector<PerUnpack> nextLevel;
510                 for (PerUnpack& p : toUnpack)
511                 {
512                     if (!p.key.starts_with(key))
513                     {
514                         continue;
515                     }
516                     std::string_view thisLeftover = p.key.substr(key.size());
517                     nextLevel.push_back({thisLeftover, p.value, false});
518                     p.complete = true;
519                 }
520 
521                 result = readJsonHelper(j, res, nextLevel) && result;
522                 break;
523             }
524 
525             result = std::visit(
526                          [&item, &unpackSpec, &res](auto&& val) {
527                 using ContainedT =
528                     std::remove_pointer_t<std::decay_t<decltype(val)>>;
529                 return details::unpackValue<ContainedT>(
530                     item.second, unpackSpec.key, res, *val);
531             },
532                          unpackSpec.value) &&
533                      result;
534 
535             unpackSpec.complete = true;
536             break;
537         }
538 
539         if (unpackIndex == toUnpack.size())
540         {
541             messages::propertyUnknown(res, item.first);
542             result = false;
543         }
544     }
545 
546     for (PerUnpack& perUnpack : toUnpack)
547     {
548         if (!perUnpack.complete)
549         {
550             bool isOptional = std::visit(
551                 [](auto&& val) {
552                 using ContainedType =
553                     std::remove_pointer_t<std::decay_t<decltype(val)>>;
554                 return details::IsOptional<ContainedType>::value;
555             },
556                 perUnpack.value);
557             if (isOptional)
558             {
559                 continue;
560             }
561             messages::propertyMissing(res, perUnpack.key);
562             result = false;
563         }
564     }
565     return result;
566 }
567 
568 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
569                            std::span<PerUnpack> toUnpack)
570 {
571     nlohmann::json::object_t* obj =
572         jsonRequest.get_ptr<nlohmann::json::object_t*>();
573     if (obj == nullptr)
574     {
575         BMCWEB_LOG_DEBUG("Json value is not an object");
576         messages::unrecognizedRequestBody(res);
577         return false;
578     }
579     return readJsonHelperObject(*obj, res, toUnpack);
580 }
581 
582 inline void packVariant(std::span<PerUnpack> /*toPack*/) {}
583 
584 template <typename FirstType, typename... UnpackTypes>
585 void packVariant(std::span<PerUnpack> toPack, std::string_view key,
586                  FirstType& first, UnpackTypes&&... in)
587 {
588     if (toPack.empty())
589     {
590         return;
591     }
592     toPack[0].key = key;
593     toPack[0].value = &first;
594     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
595     packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...);
596 }
597 
598 template <typename FirstType, typename... UnpackTypes>
599 bool readJsonObject(nlohmann::json::object_t& jsonRequest, crow::Response& res,
600                     std::string_view key, FirstType&& first,
601                     UnpackTypes&&... in)
602 {
603     const std::size_t n = sizeof...(UnpackTypes) + 2;
604     std::array<PerUnpack, n / 2> toUnpack2;
605     packVariant(toUnpack2, key, first, std::forward<UnpackTypes&&>(in)...);
606     return readJsonHelperObject(jsonRequest, res, toUnpack2);
607 }
608 
609 template <typename FirstType, typename... UnpackTypes>
610 bool readJson(nlohmann::json& jsonRequest, crow::Response& res,
611               std::string_view key, FirstType&& first, UnpackTypes&&... in)
612 {
613     nlohmann::json::object_t* obj =
614         jsonRequest.get_ptr<nlohmann::json::object_t*>();
615     if (obj == nullptr)
616     {
617         BMCWEB_LOG_DEBUG("Json value is not an object");
618         messages::unrecognizedRequestBody(res);
619         return false;
620     }
621     return readJsonObject(*obj, res, key, std::forward<FirstType>(first),
622                           std::forward<UnpackTypes&&>(in)...);
623 }
624 
625 inline std::optional<nlohmann::json::object_t>
626     readJsonPatchHelper(const crow::Request& req, crow::Response& res)
627 {
628     nlohmann::json jsonRequest;
629     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
630     {
631         BMCWEB_LOG_DEBUG("Json value not readable");
632         return std::nullopt;
633     }
634     nlohmann::json::object_t* object =
635         jsonRequest.get_ptr<nlohmann::json::object_t*>();
636     if (object == nullptr || object->empty())
637     {
638         BMCWEB_LOG_DEBUG("Json value is empty");
639         messages::emptyJSON(res);
640         return std::nullopt;
641     }
642     std::erase_if(*object,
643                   [](const std::pair<std::string, nlohmann::json>& item) {
644         return item.first.starts_with("@odata.");
645     });
646     if (object->empty())
647     {
648         //  If the update request only contains OData annotations, the service
649         //  should return the HTTP 400 Bad Request status code with the
650         //  NoOperation message from the Base Message Registry, ...
651         messages::noOperation(res);
652         return std::nullopt;
653     }
654 
655     return {std::move(*object)};
656 }
657 
658 template <typename... UnpackTypes>
659 bool readJsonPatch(const crow::Request& req, crow::Response& res,
660                    std::string_view key, UnpackTypes&&... in)
661 {
662     std::optional<nlohmann::json::object_t> jsonRequest =
663         readJsonPatchHelper(req, res);
664     if (!jsonRequest)
665     {
666         return false;
667     }
668     if (jsonRequest->empty())
669     {
670         messages::emptyJSON(res);
671         return false;
672     }
673 
674     return readJsonObject(*jsonRequest, res, key,
675                           std::forward<UnpackTypes&&>(in)...);
676 }
677 
678 template <typename... UnpackTypes>
679 bool readJsonAction(const crow::Request& req, crow::Response& res,
680                     const char* key, UnpackTypes&&... in)
681 {
682     nlohmann::json jsonRequest;
683     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
684     {
685         BMCWEB_LOG_DEBUG("Json value not readable");
686         return false;
687     }
688     nlohmann::json::object_t* object =
689         jsonRequest.get_ptr<nlohmann::json::object_t*>();
690     if (object == nullptr)
691     {
692         BMCWEB_LOG_DEBUG("Json value is empty");
693         messages::emptyJSON(res);
694         return false;
695     }
696     return readJsonObject(*object, res, key,
697                           std::forward<UnpackTypes&&>(in)...);
698 }
699 
700 // Determines if two json objects are less, based on the presence of the
701 // @odata.id key
702 inline int odataObjectCmp(const nlohmann::json& a, const nlohmann::json& b)
703 {
704     using object_t = nlohmann::json::object_t;
705     const object_t* aObj = a.get_ptr<const object_t*>();
706     const object_t* bObj = b.get_ptr<const object_t*>();
707 
708     if (aObj == nullptr)
709     {
710         if (bObj == nullptr)
711         {
712             return 0;
713         }
714         return -1;
715     }
716     if (bObj == nullptr)
717     {
718         return 1;
719     }
720     object_t::const_iterator aIt = aObj->find("@odata.id");
721     object_t::const_iterator bIt = bObj->find("@odata.id");
722     // If either object doesn't have the key, they get "sorted" to the end.
723     if (aIt == aObj->end())
724     {
725         if (bIt == bObj->end())
726         {
727             return 0;
728         }
729         return -1;
730     }
731     if (bIt == bObj->end())
732     {
733         return 1;
734     }
735     const nlohmann::json::string_t* nameA =
736         aIt->second.get_ptr<const std::string*>();
737     const nlohmann::json::string_t* nameB =
738         bIt->second.get_ptr<const std::string*>();
739     // If either object doesn't have a string as the key, they get "sorted" to
740     // the end.
741     if (nameA == nullptr)
742     {
743         if (nameB == nullptr)
744         {
745             return 0;
746         }
747         return -1;
748     }
749     if (nameB == nullptr)
750     {
751         return 1;
752     }
753     boost::urls::url_view aUrl(*nameA);
754     boost::urls::url_view bUrl(*nameB);
755     auto segmentsAIt = aUrl.segments().begin();
756     auto segmentsBIt = bUrl.segments().begin();
757 
758     while (true)
759     {
760         if (segmentsAIt == aUrl.segments().end())
761         {
762             if (segmentsBIt == bUrl.segments().end())
763             {
764                 return 0;
765             }
766             return -1;
767         }
768         if (segmentsBIt == bUrl.segments().end())
769         {
770             return 1;
771         }
772         int res = alphanumComp(*segmentsAIt, *segmentsBIt);
773         if (res != 0)
774         {
775             return res;
776         }
777 
778         segmentsAIt++;
779         segmentsBIt++;
780     }
781 };
782 
783 struct ODataObjectLess
784 {
785     bool operator()(const nlohmann::json& left,
786                     const nlohmann::json& right) const
787     {
788         return odataObjectCmp(left, right) < 0;
789     }
790 };
791 
792 // Sort the JSON array by |element[key]|.
793 // Elements without |key| or type of |element[key]| is not string are smaller
794 // those whose |element[key]| is string.
795 inline void sortJsonArrayByOData(nlohmann::json::array_t& array)
796 {
797     std::ranges::sort(array, ODataObjectLess());
798 }
799 
800 // Returns the estimated size of the JSON value
801 // The implementation walks through every key and every value, accumulates the
802 //  total size of keys and values.
803 // Ideally, we should use a custom allocator that nlohmann JSON supports.
804 
805 // Assumption made:
806 //  1. number: 8 characters
807 //  2. boolean: 5 characters (False)
808 //  3. string: len(str) + 2 characters (quote)
809 //  4. bytes: len(bytes) characters
810 //  5. null: 4 characters (null)
811 uint64_t getEstimatedJsonSize(const nlohmann::json& root);
812 
813 } // namespace json_util
814 } // namespace redfish
815