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.
15namespace utility // NOLINT(modernize-concat-nested-namespaces)
16{
17//! @brief JSON-related functions in the utility module.
18namespace json
19{
20//! @brief Brief function description.
21//! @return function description (module_function)
22inline static const char* description() noexcept
23{
24 return "UTIL_JSON";
25}
26extern const char* version() noexcept;
27
28//! @brief Javascript object notation.
29class JSON
30{
31public:
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
357private:
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
381protected:
382 friend std::ostream& operator<<(std::ostream& os, const JSON& json);
383};
384
385template <typename Type>
386auto 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)
407template <typename Value>
408std::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
415template <typename Value>
416std::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
423template <typename Value>
424std::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
431template <typename Value>
432std::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
440template <typename Item>
441void JSON::append(const Item item)
442{
443 ensureType<Array>();
444 getData<Array>()->emplace_back(item);
445}
446
447template <typename I0, typename... Is>
448void JSON::append(const I0 item0, const Is... items)
449{
450 append(item0);
451 append(items...);
452}
453
454template <typename Type>
455void 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
492template <typename Type>
493bool 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
513extern JSON array();
514extern JSON object();
515} // namespace json
516} // namespace utility
517