| 1 | //! @file json.hpp |
| 2 | //! @author ryftchen |
| 3 | //! @brief The declarations (json) in the utility module. |
| 4 | //! @version 0.1.0 |
| 5 | //! @copyright Copyright (c) 2022-2025 ryftchen. All rights reserved. |
| 6 | |
| 7 | #pragma once |
| 8 | |
| 9 | #include <deque> |
| 10 | #include <map> |
| 11 | #include <memory> |
| 12 | #include <variant> |
| 13 | |
| 14 | //! @brief The utility module. |
| 15 | namespace utility // NOLINT(modernize-concat-nested-namespaces) |
| 16 | { |
| 17 | //! @brief JSON-related functions in the utility module. |
| 18 | namespace json |
| 19 | { |
| 20 | //! @brief Brief function description. |
| 21 | //! @return function description (module_function) |
| 22 | inline static const char* description() noexcept |
| 23 | { |
| 24 | return "UTIL_JSON" ; |
| 25 | } |
| 26 | extern const char* version() noexcept; |
| 27 | |
| 28 | //! @brief Javascript object notation. |
| 29 | class JSON |
| 30 | { |
| 31 | public: |
| 32 | //! @brief Enumerate specific data types. |
| 33 | enum class Type : std::uint8_t |
| 34 | { |
| 35 | //! @brief Null. |
| 36 | null, |
| 37 | //! @brief Object. |
| 38 | object, |
| 39 | //! @brief Array. |
| 40 | array, |
| 41 | //! @brief String. |
| 42 | string, |
| 43 | //! @brief Floating. |
| 44 | floating, |
| 45 | //! @brief Integral. |
| 46 | integral, |
| 47 | //! @brief Boolean. |
| 48 | boolean |
| 49 | }; |
| 50 | //! @brief Alias for the JSON null. |
| 51 | using Null = std::nullptr_t; |
| 52 | //! @brief Alias for the JSON object. |
| 53 | using Object = std::map<std::string, JSON>; |
| 54 | //! @brief Alias for the JSON array. |
| 55 | using Array = std::deque<JSON>; |
| 56 | //! @brief Alias for the JSON string. |
| 57 | using String = std::string; |
| 58 | //! @brief Alias for the JSON number (floating). |
| 59 | using Floating = double; |
| 60 | //! @brief Alias for the JSON number (integral). |
| 61 | using Integral = long long; |
| 62 | //! @brief Alias for the JSON boolean. |
| 63 | using Boolean = bool; |
| 64 | |
| 65 | //! @brief Construct a new JSON object. |
| 66 | JSON() = default; |
| 67 | //! @brief Construct a new JSON object. |
| 68 | //! @param type - data type |
| 69 | explicit JSON(const Type type); |
| 70 | //! @brief Construct a new JSON object. |
| 71 | //! @param list - initializer list for JSON |
| 72 | JSON(const std::initializer_list<JSON>& list); |
| 73 | //! @brief Destroy the JSON object. |
| 74 | virtual ~JSON() = default; |
| 75 | //! @brief Construct a new JSON object. |
| 76 | JSON(const JSON&) = default; |
| 77 | //! @brief Construct a new JSON object. |
| 78 | //! @param json - object for move constructor |
| 79 | JSON(JSON&& json) noexcept; |
| 80 | //! @brief The operator (=) overloading of JSON class. |
| 81 | //! @return reference of the JSON object |
| 82 | JSON& operator=(const JSON&) = default; |
| 83 | //! @brief The operator (=) overloading of JSON class. |
| 84 | //! @param json - object for move assignment operator |
| 85 | //! @return reference of the JSON object |
| 86 | JSON& operator=(JSON&& json) noexcept; |
| 87 | |
| 88 | // NOLINTBEGIN(google-explicit-constructor) |
| 89 | //! @brief Construct a new JSON object. |
| 90 | JSON(const std::nullptr_t /*null*/) {} |
| 91 | //! @brief Construct a new JSON object. |
| 92 | //! @tparam Value - type of string value |
| 93 | //! @param s - string value |
| 94 | template <typename Value> |
| 95 | JSON(const Value s, std::enable_if_t<std::is_convertible_v<Value, std::string>>* /*type*/ = nullptr) : |
| 96 | data{String{s}} |
| 97 | { |
| 98 | } |
| 99 | //! @brief Construct a new JSON object. |
| 100 | //! @tparam Value - type of floating value |
| 101 | //! @param f - floating value |
| 102 | template <typename Value> |
| 103 | JSON(const Value f, std::enable_if_t<std::is_floating_point_v<Value>>* /*type*/ = nullptr) : |
| 104 | data{static_cast<Floating>(f)} |
| 105 | { |
| 106 | } |
| 107 | //! @brief Construct a new JSON object. |
| 108 | //! @tparam Value - type of integral value |
| 109 | //! @param i - integral value |
| 110 | template <typename Value> |
| 111 | JSON( |
| 112 | const Value i, |
| 113 | std::enable_if_t<std::is_integral_v<Value> && !std::is_same_v<Value, bool>>* /*type*/ = nullptr) : |
| 114 | data{static_cast<Integral>(i)} |
| 115 | { |
| 116 | } |
| 117 | //! @brief Construct a new JSON object. |
| 118 | //! @tparam Value - type of boolean value |
| 119 | //! @param b - boolean value |
| 120 | template <typename Value> |
| 121 | JSON(const Value b, std::enable_if_t<std::is_same_v<Value, bool>>* /*type*/ = nullptr) : |
| 122 | data{static_cast<Boolean>(b)} |
| 123 | { |
| 124 | } |
| 125 | // NOLINTEND(google-explicit-constructor) |
| 126 | // NOLINTBEGIN(misc-unconventional-assign-operator) |
| 127 | //! @brief The operator (=) overloading of JSON class. |
| 128 | //! @tparam Value - type of string value |
| 129 | //! @param s - string value |
| 130 | //! @return reference of the JSON object |
| 131 | template <typename Value> |
| 132 | std::enable_if_t<std::is_convertible_v<Value, std::string>, JSON&> operator=(const Value s); |
| 133 | //! @brief The operator (=) overloading of JSON class. |
| 134 | //! @tparam Value - type of floating value |
| 135 | //! @param f - floating value |
| 136 | //! @return reference of the JSON object |
| 137 | template <typename Value> |
| 138 | std::enable_if_t<std::is_floating_point_v<Value>, JSON&> operator=(const Value f); |
| 139 | //! @brief The operator (=) overloading of JSON class. |
| 140 | //! @tparam Value - type of integral value |
| 141 | //! @param i - integral value |
| 142 | //! @return reference of the JSON object |
| 143 | template <typename Value> |
| 144 | std::enable_if_t<std::is_integral_v<Value> && !std::is_same_v<Value, bool>, JSON&> operator=(const Value i); |
| 145 | //! @brief The operator (=) overloading of JSON class. |
| 146 | //! @tparam Value - type of boolean value |
| 147 | //! @param b - boolean value |
| 148 | //! @return reference of the JSON object |
| 149 | template <typename Value> |
| 150 | std::enable_if_t<std::is_same_v<Value, bool>, JSON&> operator=(const Value b); |
| 151 | // NOLINTEND(misc-unconventional-assign-operator) |
| 152 | |
| 153 | //! @brief JSON wrapper. |
| 154 | //! @tparam Container - type of container |
| 155 | template <typename Container> |
| 156 | class JSONWrapper |
| 157 | { |
| 158 | public: |
| 159 | //! @brief Construct a new JSONWrapper object. |
| 160 | //! @param container - JSON object to be wrapped |
| 161 | explicit JSONWrapper(Container* container) : object{container} {} |
| 162 | //! @brief Construct a new JSONWrapper object. |
| 163 | explicit JSONWrapper(std::nullptr_t /*null*/) {} |
| 164 | |
| 165 | //! @brief Pointer to the JSON object. |
| 166 | Container* object{nullptr}; |
| 167 | //! @brief Get the first iterator. |
| 168 | //! @return first iterator |
| 169 | typename Container::iterator begin() { return object ? object->begin() : typename Container::iterator(); } |
| 170 | //! @brief Get the last iterator. |
| 171 | //! @return last iterator |
| 172 | typename Container::iterator end() { return object ? object->end() : typename Container::iterator(); } |
| 173 | //! @brief Get the first const iterator. |
| 174 | //! @return first const iterator |
| 175 | typename Container::const_iterator begin() const |
| 176 | { |
| 177 | return object ? object->begin() : typename Container::iterator(); |
| 178 | } |
| 179 | //! @brief Get the last const iterator. |
| 180 | //! @return last const iterator |
| 181 | typename Container::const_iterator end() const |
| 182 | { |
| 183 | return object ? object->end() : typename Container::iterator(); |
| 184 | } |
| 185 | }; |
| 186 | |
| 187 | //! @brief JSON wrapper. |
| 188 | //! @tparam Container - type of container |
| 189 | template <typename Container> |
| 190 | class JSONConstWrapper |
| 191 | { |
| 192 | public: |
| 193 | //! @brief Construct a new JSONWrapper object. |
| 194 | //! @param container - JSON object to be wrapped |
| 195 | explicit JSONConstWrapper(const Container* container) : object{container} {} |
| 196 | //! @brief Construct a new JSONWrapper object. |
| 197 | explicit JSONConstWrapper(const std::nullptr_t /*null*/) {} |
| 198 | |
| 199 | //! @brief Const pointer to the JSON object. |
| 200 | const Container* object{nullptr}; |
| 201 | //! @brief Get the first const iterator. |
| 202 | //! @return first const iterator |
| 203 | typename Container::const_iterator begin() const |
| 204 | { |
| 205 | return object ? object->begin() : typename Container::const_iterator(); |
| 206 | } |
| 207 | //! @brief Get the last const iterator. |
| 208 | //! @return last const iterator |
| 209 | typename Container::const_iterator end() const |
| 210 | { |
| 211 | return object ? object->end() : typename Container::const_iterator(); |
| 212 | } |
| 213 | }; |
| 214 | |
| 215 | //! @brief Make JSON object by data type. |
| 216 | //! @param type - data type |
| 217 | //! @return JSON object |
| 218 | static JSON make(const Type type); |
| 219 | //! @brief Load JSON object from string. |
| 220 | //! @param fmt - formatted string to be parsed |
| 221 | //! @return JSON object |
| 222 | static JSON load(const std::string_view fmt); |
| 223 | //! @brief Append item to array. Convert to array type. |
| 224 | //! @tparam Item - type of item |
| 225 | //! @param item - item to append |
| 226 | template <typename Item> |
| 227 | void append(const Item item); |
| 228 | //! @brief Append multiple items to array. Convert to array type. |
| 229 | //! @tparam I0 - type of first item |
| 230 | //! @tparam Is - type of multiple items |
| 231 | //! @param item0 - first item to append |
| 232 | //! @param items - multiple items to append |
| 233 | template <typename I0, typename... Is> |
| 234 | void append(const I0 item0, const Is... items); |
| 235 | //! @brief The operator ([]) overloading of JSON class. |
| 236 | //! @param key - target key |
| 237 | //! @return reference of the JSON object |
| 238 | JSON& operator[](const std::string& key); |
| 239 | //! @brief The operator ([]) overloading of JSON class. |
| 240 | //! @param index - target index |
| 241 | //! @return reference of the JSON object |
| 242 | JSON& operator[](std::size_t index); |
| 243 | //! @brief Get the JSON object by key. |
| 244 | //! @param key - target key |
| 245 | //! @return reference of the JSON object |
| 246 | JSON& at(const std::string& key); |
| 247 | //! @brief Get the JSON object by key. |
| 248 | //! @param key - target key |
| 249 | //! @return const reference of the JSON object |
| 250 | [[nodiscard]] const JSON& at(const std::string& key) const; |
| 251 | //! @brief Get the JSON object by index. |
| 252 | //! @param index - target index |
| 253 | //! @return reference of the JSON object |
| 254 | JSON& at(std::size_t index); |
| 255 | //! @brief Get the JSON object by index. |
| 256 | //! @param index - target index |
| 257 | //! @return const reference of the JSON object |
| 258 | [[nodiscard]] const JSON& at(std::size_t index) const; |
| 259 | //! @brief Get the length of the array. |
| 260 | //! @return number of items stored in the array, -1 if type is not array |
| 261 | [[nodiscard]] int length() const; |
| 262 | //! @brief Get the size of the array or object. |
| 263 | //! @return number of items stored in the array or object, -1 if type is neither array nor object |
| 264 | [[nodiscard]] int size() const; |
| 265 | //! @brief Check whether the key exists. |
| 266 | //! @param key - target key |
| 267 | //! @return exist or not |
| 268 | [[nodiscard]] bool hasKey(const std::string& key) const; |
| 269 | //! @brief Check whether the type is null. |
| 270 | //! @return be null type or not |
| 271 | [[nodiscard]] bool isNullType() const; |
| 272 | //! @brief Check whether the type is object. |
| 273 | //! @return be object type or not |
| 274 | [[nodiscard]] bool isObjectType() const; |
| 275 | //! @brief Check whether the type is array. |
| 276 | //! @return be array type or not |
| 277 | [[nodiscard]] bool isArrayType() const; |
| 278 | //! @brief Check whether the type is string. |
| 279 | //! @return be string type or not |
| 280 | [[nodiscard]] bool isStringType() const; |
| 281 | //! @brief Check whether the type is floating. |
| 282 | //! @return be floating type or not |
| 283 | [[nodiscard]] bool isFloatingType() const; |
| 284 | //! @brief Check whether the type is integral. |
| 285 | //! @return be integral type or not |
| 286 | [[nodiscard]] bool isIntegralType() const; |
| 287 | //! @brief Check whether the type is boolean. |
| 288 | //! @return be boolean type or not |
| 289 | [[nodiscard]] bool isBooleanType() const; |
| 290 | //! @brief Convert to string value. |
| 291 | //! @return string value |
| 292 | [[nodiscard]] String asString() const; |
| 293 | //! @brief Convert to unescaped string value. |
| 294 | //! @return unescaped string value |
| 295 | [[nodiscard]] String asUnescapedString() const; |
| 296 | //! @brief Convert to floating value. |
| 297 | //! @return floating value |
| 298 | [[nodiscard]] Floating asFloating() const; |
| 299 | //! @brief Convert to integral value. |
| 300 | //! @return integral value |
| 301 | [[nodiscard]] Integral asIntegral() const; |
| 302 | //! @brief Convert to boolean value. |
| 303 | //! @return boolean value |
| 304 | [[nodiscard]] Boolean asBoolean() const; |
| 305 | //! @brief Get the wrapper of the object range. |
| 306 | //! @return wrapper of the object range |
| 307 | JSONWrapper<Object> objectRange(); |
| 308 | //! @brief Get the wrapper of the array range. |
| 309 | //! @return wrapper of the array range |
| 310 | JSONWrapper<Array> arrayRange(); |
| 311 | //! @brief Get the wrapper of the object range. |
| 312 | //! @return wrapper of the object range |
| 313 | [[nodiscard]] JSONConstWrapper<Object> objectRange() const; |
| 314 | //! @brief Get the wrapper of the array range. |
| 315 | //! @return wrapper of the array range |
| 316 | [[nodiscard]] JSONConstWrapper<Array> arrayRange() const; |
| 317 | //! @brief Dump as formatted string. |
| 318 | //! @param depth - target depth |
| 319 | //! @param tab - tab string |
| 320 | //! @return formatted string |
| 321 | [[nodiscard]] std::string dump(const std::uint32_t depth = 1, const std::string_view tab = " " ) const; |
| 322 | //! @brief Dump as minified formatted string. |
| 323 | //! @return minified formatted string |
| 324 | [[nodiscard]] std::string dumpMinified() const; |
| 325 | |
| 326 | //! @brief The data that stores JSON information. |
| 327 | struct Data |
| 328 | { |
| 329 | //! @brief Construct a new Data object. |
| 330 | Data() : value{nullptr} {} |
| 331 | //! @brief Construct a new Data object. |
| 332 | //! @param s - string value |
| 333 | explicit Data(const String& s) : value{std::make_shared<String>(args: s)} {} |
| 334 | //! @brief Construct a new Data object. |
| 335 | //! @param f - floating value |
| 336 | explicit Data(const Floating f) : value{f} {} |
| 337 | //! @brief Construct a new Data object. |
| 338 | //! @param i - integral value |
| 339 | explicit Data(const Integral i) : value{i} {} |
| 340 | //! @brief Construct a new Data object. |
| 341 | //! @param b - boolean value |
| 342 | explicit Data(const Boolean b) : value{b} {} |
| 343 | |
| 344 | //! @brief Alias for the pointer of Object. Non-fundamental type. |
| 345 | using ObjectPtr = std::shared_ptr<Object>; |
| 346 | //! @brief Alias for the pointer of Array. Non-fundamental type. |
| 347 | using ArrayPtr = std::shared_ptr<Array>; |
| 348 | //! @brief Alias for the pointer of String. Non-fundamental type. |
| 349 | using StringPtr = std::shared_ptr<String>; |
| 350 | //! @brief Alias for the data's value type. |
| 351 | using ValueType = |
| 352 | std::variant<std::monostate, Null, ObjectPtr, ArrayPtr, StringPtr, Floating, Integral, Boolean>; |
| 353 | //! @brief Value of the data. |
| 354 | ValueType value; |
| 355 | } /** @brief JSON valid data. */ data; |
| 356 | |
| 357 | private: |
| 358 | //! @brief Ensure the target type has been set. |
| 359 | //! @tparam Type - type of data |
| 360 | template <typename Type> |
| 361 | void ensureType(); |
| 362 | //! @brief Check whether it holds data. |
| 363 | //! @tparam Type - type of data |
| 364 | //! @return holds or not |
| 365 | template <typename Type> |
| 366 | [[nodiscard]] bool holdsData() const; |
| 367 | //! @brief Get the data. |
| 368 | //! @tparam Type - type of data |
| 369 | //! @return data value |
| 370 | template <typename Type> |
| 371 | [[nodiscard]] auto getData() const; |
| 372 | |
| 373 | //! @brief Data's value object's helper type for the visitor. |
| 374 | //! @tparam Ts - type of visitors |
| 375 | template <typename... Ts> |
| 376 | struct ValueVisitor : public Ts... |
| 377 | { |
| 378 | using Ts::operator()...; |
| 379 | }; |
| 380 | |
| 381 | protected: |
| 382 | friend std::ostream& operator<<(std::ostream& os, const JSON& json); |
| 383 | }; |
| 384 | |
| 385 | template <typename Type> |
| 386 | auto JSON::getData() const |
| 387 | { |
| 388 | if constexpr (std::is_same_v<Type, Object>) |
| 389 | { |
| 390 | return std::get<Data::ObjectPtr>(v: data.value); |
| 391 | } |
| 392 | else if constexpr (std::is_same_v<Type, Array>) |
| 393 | { |
| 394 | return std::get<Data::ArrayPtr>(v: data.value); |
| 395 | } |
| 396 | else if constexpr (std::is_same_v<Type, String>) |
| 397 | { |
| 398 | return std::get<Data::StringPtr>(v: data.value); |
| 399 | } |
| 400 | else |
| 401 | { |
| 402 | return std::get<Type>(data.value); |
| 403 | } |
| 404 | } |
| 405 | |
| 406 | // NOLINTBEGIN(misc-unconventional-assign-operator) |
| 407 | template <typename Value> |
| 408 | std::enable_if_t<std::is_convertible_v<Value, std::string>, JSON&> JSON::operator=(const Value s) |
| 409 | { |
| 410 | ensureType<String>(); |
| 411 | *getData<String>() = String{s}; |
| 412 | return *this; |
| 413 | } |
| 414 | |
| 415 | template <typename Value> |
| 416 | std::enable_if_t<std::is_floating_point_v<Value>, JSON&> JSON::operator=(const Value f) |
| 417 | { |
| 418 | ensureType<Floating>(); |
| 419 | data.value = static_cast<Floating>(f); |
| 420 | return *this; |
| 421 | } |
| 422 | |
| 423 | template <typename Value> |
| 424 | std::enable_if_t<std::is_integral_v<Value> && !std::is_same_v<Value, bool>, JSON&> JSON::operator=(const Value i) |
| 425 | { |
| 426 | ensureType<Integral>(); |
| 427 | data.value = static_cast<Integral>(i); |
| 428 | return *this; |
| 429 | } |
| 430 | |
| 431 | template <typename Value> |
| 432 | std::enable_if_t<std::is_same_v<Value, bool>, JSON&> JSON::operator=(const Value b) |
| 433 | { |
| 434 | ensureType<Boolean>(); |
| 435 | data.value = static_cast<Boolean>(b); |
| 436 | return *this; |
| 437 | } |
| 438 | // NOLINTEND(misc-unconventional-assign-operator) |
| 439 | |
| 440 | template <typename Item> |
| 441 | void JSON::append(const Item item) |
| 442 | { |
| 443 | ensureType<Array>(); |
| 444 | getData<Array>()->emplace_back(item); |
| 445 | } |
| 446 | |
| 447 | template <typename I0, typename... Is> |
| 448 | void JSON::append(const I0 item0, const Is... items) |
| 449 | { |
| 450 | append(item0); |
| 451 | append(items...); |
| 452 | } |
| 453 | |
| 454 | template <typename Type> |
| 455 | void JSON::ensureType() |
| 456 | { |
| 457 | if (holdsData<Type>()) |
| 458 | { |
| 459 | return; |
| 460 | } |
| 461 | |
| 462 | if constexpr (std::is_same_v<Type, Null>) |
| 463 | { |
| 464 | data = Data{}; |
| 465 | } |
| 466 | else if constexpr (std::is_same_v<Type, Object>) |
| 467 | { |
| 468 | data.value = std::make_shared<Object>(); |
| 469 | } |
| 470 | else if constexpr (std::is_same_v<Type, Array>) |
| 471 | { |
| 472 | data.value = std::make_shared<Array>(); |
| 473 | } |
| 474 | else if constexpr (std::is_same_v<Type, String>) |
| 475 | { |
| 476 | data.value = std::make_shared<String>(); |
| 477 | } |
| 478 | else if constexpr (std::is_same_v<Type, Floating>) |
| 479 | { |
| 480 | data.value = static_cast<Floating>(0.0); |
| 481 | } |
| 482 | else if constexpr (std::is_same_v<Type, Integral>) |
| 483 | { |
| 484 | data.value = static_cast<Integral>(0); |
| 485 | } |
| 486 | else if constexpr (std::is_same_v<Type, Boolean>) |
| 487 | { |
| 488 | data.value = static_cast<Boolean>(false); |
| 489 | } |
| 490 | } |
| 491 | |
| 492 | template <typename Type> |
| 493 | bool JSON::holdsData() const |
| 494 | { |
| 495 | if constexpr (std::is_same_v<Type, Object>) |
| 496 | { |
| 497 | return std::holds_alternative<Data::ObjectPtr>(v: data.value); |
| 498 | } |
| 499 | else if constexpr (std::is_same_v<Type, Array>) |
| 500 | { |
| 501 | return std::holds_alternative<Data::ArrayPtr>(v: data.value); |
| 502 | } |
| 503 | else if constexpr (std::is_same_v<Type, String>) |
| 504 | { |
| 505 | return std::holds_alternative<Data::StringPtr>(v: data.value); |
| 506 | } |
| 507 | else |
| 508 | { |
| 509 | return std::holds_alternative<Type>(data.value); |
| 510 | } |
| 511 | } |
| 512 | |
| 513 | extern JSON array(); |
| 514 | extern JSON object(); |
| 515 | } // namespace json |
| 516 | } // namespace utility |
| 517 | |