Branch data Line data Source code
1 : : /*
2 : : * The MIT License (Mitem)
3 : : *
4 : : * Copyright (c) 2024 Yaroslav Riabtsev <yaroslav.riabtsev@rwth-aachen.de>
5 : : *
6 : : * Permission is hereby granted, free of charge, to any person obtaining a copy
7 : : * of this software and associated documentation files (the "Software"), to deal
8 : : * in the Software without restriction, including without limitation the rights
9 : : * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 : : * copies of the Software, and to permit persons to whom the Software is
11 : : * furnished to do so, subject to the following conditions:
12 : : *
13 : : * The above copyright notice and this permission notice shall be included
14 : : * in all copies or substantial portions of the Software.
15 : : *
16 : : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 : : * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 : : * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
19 : : * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 : : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 : : * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 : : * SOFTWARE.
23 : : */
24 : :
25 : : #ifndef JSON_HPP
26 : : #define JSON_HPP
27 : : #include <memory>
28 : : #include <source_location>
29 : : #include <string>
30 : : #include <unordered_map>
31 : : #include <vector>
32 : :
33 : : namespace json_lib {
34 : : /**
35 : : * @brief Enable or disable symmetric indexing for JSON objects.
36 : : *
37 : : * When `enable_symmetric_indexing` is set to `true`, symmetric indexing allows
38 : : * for flexible access, similar to C-style dynamic arrays where `arr[index] ==
39 : : * index[arr]`. For JSON objects, this feature enables interchangeable access to
40 : : * keys and values in specific cases, allowing for concise access patterns.
41 : : *
42 : : * ### Example with JSON
43 : : * Consider a JSON structure like:
44 : : * ```json
45 : : * {
46 : : * "obj": {
47 : : * "secret_key": 5,
48 : : * "public_arr": ["key", "key", "secret_key", "key"]
49 : : * }
50 : : * }
51 : : * ```
52 : : * With symmetric indexing enabled, instead of writing `obj[obj.public_arr[2]]`,
53 : : * you could write `obj.public_arr[2][obj]`, which accesses `"secret_key"` in a
54 : : * more concise form.
55 : : *
56 : : * ### How it looks in source code
57 : : * In C++ code, enabling symmetric indexing can simplify complex JSON access
58 : : * patterns:
59 : : * ```cpp
60 : : * // Define JSON object with shared pointers
61 : : * std::vector<std::pair<std::string, std::shared_ptr<json_lib::json>>> values =
62 : : * {
63 : : * { "secret_key", std::make_shared<json_lib::json_integer>(5) },
64 : : * { "public_arr", std::make_shared<json_lib::json_array>(
65 : : * std::vector<std::shared_ptr<json_lib::json>>{
66 : : * std::make_shared<json_lib::json_string>("key"),
67 : : * std::make_shared<json_lib::json_string>("key"),
68 : : * std::make_shared<json_lib::json_string>("secret_key"),
69 : : * std::make_shared<json_lib::json_string>("key")
70 : : * })
71 : : * }
72 : : * };
73 : : * std::shared_ptr<json_lib::json_object> obj =
74 : : * std::make_shared<json_lib::json_object>(values);
75 : : * assert(obj->at("public_arr")->at(2)->by(obj) ==
76 : : * obj->by(obj->at("public_arr")->at(2)) &&
77 : : * "Symmetric indexing is enabled");
78 : : * ```
79 : : * This demonstrates how symmetric indexing can simplify access to values in
80 : : * nested structures, as shown in the JSON example above.
81 : : *
82 : : * **Default:** `false`
83 : : */
84 : : inline bool enable_symmetric_indexing = false;
85 : :
86 : : /**
87 : : * @brief Enable or disable support for negative array indexing.
88 : : *
89 : : * When `enable_negative_indexing` is set to `true`, negative indices are
90 : : * supported in arrays, similar to Python-style indexing. This allows for
91 : : * reverse-order element access, where `array[-index]` retrieves elements
92 : : * from the end, computed as `array[size - index]`.
93 : : *
94 : : * ### Example with JSON
95 : : * In a JSON array structure like:
96 : : * ```json
97 : : * {
98 : : * "arr": [1, 1.0, true, "test"]
99 : : * }
100 : : * ```
101 : : * Accessing `arr[-1]` would be equivalent to `arr[3]` (last element, because
102 : : * `arr.size() == 4`), returning `"test"`.
103 : : *
104 : : * ### How it looks in source code
105 : : * In C++ code, using `shared_ptr` for JSON elements:
106 : : * ```cpp
107 : : * std::vector<std::shared_ptr<json_lib::json>> values = {
108 : : * std::make_shared<json_lib::json_integer>(1),
109 : : * std::make_shared<json_lib::json_real>(1.0f),
110 : : * std::make_shared<json_lib::json_boolean>(true),
111 : : * std::make_shared<json_lib::json_string>("test")
112 : : * };
113 : : * std::shared_ptr<json_lib::json_array> arr =
114 : : * std::make_shared<json_lib::json_array>(values);
115 : : * assert(arr->size() == 4 &&
116 : : * "Array contains 4 elements");
117 : : * assert(arr->at(-1) == arr->at(3) &&
118 : : * arr->at(-1)->to_string() == "\"test\"" &&
119 : : * "Negative indexing is enabled");
120 : : * ```
121 : : * This demonstrates how negative indexing retrieves the last element,
122 : : * similar to the JSON example above.
123 : : *
124 : : * **Default:** `false`
125 : : */
126 : : inline bool enable_negative_indexing = false;
127 : :
128 : : /**
129 : : * @brief Enumeration representing the different types of JSON values.
130 : : *
131 : : * The `json_type` enumeration defines the various types of JSON values that
132 : : * can be represented in the library. Each type corresponds to a fundamental
133 : : * data structure in JSON, allowing for type checking and handling of
134 : : * different JSON elements.
135 : : */
136 : : enum class json_type : int {
137 : : null_json, ///< Represents a JSON null value.
138 : : boolean_json, ///< Represents a JSON boolean value (true or false).
139 : : integer_json, ///< Represents a JSON integer value.
140 : : real_json, ///< Represents a JSON real (floating-point) value.
141 : : string_json, ///< Represents a JSON string value.
142 : : array_json, ///< Represents a JSON array.
143 : : object_json, ///< Represents a JSON object.
144 : : reference_json, ///< Represents an abstract JSON type, referring to another
145 : : ///< JSON value.
146 : : set_json,
147 : : function_json
148 : : };
149 : :
150 : : /**
151 : : * @brief Convert a `json_type` to its corresponding string representation.
152 : : *
153 : : * The `json_type_to_string` function takes a `json_type` value and converts
154 : : * it to a human-readable string. This can be useful for debugging or logging
155 : : * purposes, allowing users to easily identify the type of JSON value.
156 : : *
157 : : * @param type The `json_type` to convert to a string.
158 : : * @return A string representation of the given `json_type`.
159 : : */
160 : : std::string json_type_to_string(json_type type);
161 : :
162 : : /**
163 : : * @brief Base class representing a JSON value.
164 : : *
165 : : * The `json` class serves as the abstract base class for all JSON types in the
166 : : * library. It provides a common interface for accessing and manipulating JSON
167 : : * values, supporting features like type identification, string conversion, and
168 : : * recursive processing while handling potential circular references.
169 : : */
170 : : class json : public std::enable_shared_from_this<json> {
171 : : public:
172 : 13589 : virtual ~json() = default;
173 : :
174 : : /**
175 : : * @brief Get the JSON type of the object.
176 : : *
177 : : * This method returns the type of the JSON value represented by this
178 : : * object. It allows users to determine the specific kind of JSON element
179 : : * (e.g., null, boolean, integer) at runtime.
180 : : *
181 : : * @return The `json_type` of the object.
182 : : */
183 : 1077 : virtual constexpr json_type type() const { return _type; }
184 : :
185 : : /**
186 : : * @brief Prevents infinite loops by marking objects during recursive JSON
187 : : * processing.
188 : : *
189 : : * This virtual method is used to safeguard against infinite loops, such as
190 : : * in circular references, by "touching" the JSON object. During recursive
191 : : * operations, this function enables detection of repeated references to
192 : : * halt processing if a loop is detected.
193 : : */
194 : 94 : virtual void touch() { }
195 : :
196 : : virtual void set_root(const std::shared_ptr<json>& item);
197 : :
198 : : /**
199 : : * @brief Check if the JSON element is empty.
200 : : *
201 : : * This method determines whether the JSON element contains any nested
202 : : * values. For collection types such as `json_array` and `json_object`, if
203 : : * their size is zero, it means the collection is empty and contains no
204 : : * nested values.
205 : : *
206 : : * @return `true` if the JSON element is empty; `false` otherwise.
207 : : */
208 : : virtual bool empty() const;
209 : : /**
210 : : * @brief Specifies if the JSON element is consistently represented in a
211 : : * compact, single-line format.
212 : : *
213 : : * This method returns `true` if the JSON element is always displayed in a
214 : : * compact, single-line format, regardless of formatting options like
215 : : * `pretty` or `indent_level`. Compact elements include simple types,
216 : : * such as integers or strings, and empty structures, like empty arrays or
217 : : * objects, which do not require additional formatting.
218 : : *
219 : : * When `compact()` returns `false`, the JSON element may support multi-line
220 : : * formatting with indentation and line breaks, particularly for complex or
221 : : * nested structures.
222 : : *
223 : : * @return `true` if the JSON element is consistently represented in a
224 : : * single-line, compact format; `false` if multi-line or structured
225 : : * formatting may apply.
226 : : */
227 : : virtual bool compact() const;
228 : :
229 : : /**
230 : : * @brief Convert the JSON element to a string.
231 : : *
232 : : * This method generates a string representation of the JSON element by
233 : : * calling `formatted_string` with the `pretty` flag set to `false`. It
234 : : * provides a compact representation of the JSON value without additional
235 : : * formatting.
236 : : *
237 : : * @return A string representation of the JSON element in a compact format.
238 : : */
239 : : virtual std::string to_string() const;
240 : :
241 : : /**
242 : : * @brief Convert the JSON element to a formatted string.
243 : : *
244 : : * This method generates a string representation of the JSON element with
245 : : * formatting applied based on the `pretty` flag. If `pretty` is true,
246 : : * the output will be structured with indentation and line breaks for better
247 : : * readability; otherwise, it will return a compact representation.
248 : : *
249 : : * @param pretty Flag indicating whether to format the output for
250 : : * readability.
251 : : * @return A formatted string representation of the JSON element.
252 : : */
253 : : virtual std::string formatted_string(bool pretty) const;
254 : :
255 : : /**
256 : : * @brief Convert the JSON element to an indented string representation.
257 : : *
258 : : * This method generates a string representation of the JSON element with
259 : : * a specified indentation level and formatting applied based on the
260 : : * `pretty` flag. This is particularly useful for generating well-structured
261 : : * output.
262 : : *
263 : : * @param indent_level The number of spaces to use for indentation.
264 : : * @param pretty Flag indicating whether to format the output for
265 : : * readability.
266 : : * @return An indented string representation of the JSON element.
267 : : */
268 : : virtual std::string indented_string(size_t indent_level, bool pretty) const;
269 : :
270 : : /**
271 : : * @brief Retrieve a nested JSON element by another JSON value.
272 : : *
273 : : * This method serves as an alternative to `at()`, allowing access to nested
274 : : * values using another JSON object as a key or index. While `at()` works
275 : : * only with constant types (like int or string for sets), `by()` can use
276 : : * JSON values for more flexible indexing. It also supports symmetric and
277 : : * negative indexing if those features are enabled.
278 : : *
279 : : * @param item A shared pointer to a JSON object that serves as the key or
280 : : * index to access a nested value.
281 : : * @return A shared pointer to the nested JSON element corresponding to the
282 : : * provided key or index.
283 : : */
284 : : [[nodiscard]] virtual std::shared_ptr<json>
285 : : by(const std::shared_ptr<json>& item) const;
286 : :
287 : : protected:
288 : : json_type _type = json_type::null_json; ///< The type of the JSON object.
289 : : };
290 : :
291 : : /**
292 : : * @brief Represents a JSON boolean value (`true` or `false`).
293 : : *
294 : : * The `json_boolean` class encapsulates a JSON boolean value, providing a
295 : : * consistent interface for handling JSON booleans within the library. This type
296 : : * is specialized to represent only `true` or `false` values.
297 : : */
298 : : class json_boolean final : public json {
299 : : public:
300 : : /**
301 : : * @brief Constructs a JSON boolean with the specified value.
302 : : *
303 : : * Initializes the `json_boolean` instance with the given boolean value.
304 : : *
305 : : * @param value The boolean value (`true` or `false`) to be represented.
306 : : */
307 : : explicit json_boolean(bool value);
308 : :
309 : : /**
310 : : * @brief Returns a string representation of the JSON boolean value.
311 : : *
312 : : * This method returns `"true"` or `"false"` based on the boolean value,
313 : : * regardless of `indent_level` or `pretty` parameters, as these do not
314 : : * affect boolean formatting.
315 : : *
316 : : * @param indent_level Unused for `json_boolean`.
317 : : * @param pretty Unused for `json_boolean`.
318 : : * @return A string representation of the JSON boolean value.
319 : : */
320 : : std::string
321 : : indented_string(size_t indent_level, bool pretty) const override;
322 : :
323 : : private:
324 : : bool value; ///< The boolean value represented by this JSON element.
325 : : };
326 : :
327 : : class json_integer final : public json {
328 : : public:
329 : : explicit json_integer(int value);
330 : : std::string
331 : : indented_string(size_t indent_level, bool pretty) const override;
332 : : [[nodiscard]] int as_index() const;
333 : : [[nodiscard]] std::shared_ptr<json> by(const std::shared_ptr<json>& item
334 : : ) const override;
335 : :
336 : : private:
337 : : int value;
338 : : };
339 : :
340 : : class json_real final : public json {
341 : : public:
342 : : explicit json_real(float value);
343 : : explicit json_real(const std::string& str_value);
344 : : std::string
345 : : indented_string(size_t indent_level, bool pretty) const override;
346 : :
347 : : private:
348 : : float value;
349 : : std::string str_value;
350 : : };
351 : :
352 : : class json_string final : public json {
353 : : public:
354 : : explicit json_string(std::string value);
355 : : std::string
356 : : indented_string(size_t indent_level, bool pretty) const override;
357 : : [[nodiscard]] std::string as_key() const;
358 : : [[nodiscard]] std::shared_ptr<json> by(const std::shared_ptr<json>& item
359 : : ) const override;
360 : :
361 : : private:
362 : : std::string value;
363 : : };
364 : :
365 : : class json_array final : public json {
366 : : public:
367 : : explicit json_array(const std::vector<std::shared_ptr<json>>& arr = {});
368 : : void touch() override;
369 : : void set_root(const std::shared_ptr<json>& item) override;
370 : : bool empty() const override;
371 : : bool compact() const override;
372 : : std::string
373 : : indented_string(size_t indent_level, bool pretty) const override;
374 : : [[nodiscard]] size_t size() const;
375 : : [[nodiscard]] std::shared_ptr<json> at(int index) const;
376 : : [[nodiscard]] std::shared_ptr<json> by(const std::shared_ptr<json>& item
377 : : ) const override;
378 : :
379 : : private:
380 : : bool looped { false };
381 : : bool touched { false };
382 : : std::vector<std::shared_ptr<json>> list;
383 : :
384 : : static std::string format_item(
385 : : const std::shared_ptr<json>& item, size_t nested_level, bool pretty
386 : : );
387 : : };
388 : :
389 : : class json_object final : public json {
390 : : public:
391 : : explicit json_object(
392 : : const std::vector<std::pair<std::string, std::shared_ptr<json>>>& obj
393 : : = {}
394 : : );
395 : : void touch() override;
396 : : void set_root(const std::shared_ptr<json>& item) override;
397 : : bool empty() const override;
398 : : bool compact() const override;
399 : : std::string
400 : : indented_string(size_t indent_level, bool pretty) const override;
401 : : [[nodiscard]] size_t size() const;
402 : : [[nodiscard]] std::vector<std::string> get_keys();
403 : : [[nodiscard]] std::shared_ptr<json> at(const std::string& key) const;
404 : : [[nodiscard]] std::shared_ptr<json> by(const std::shared_ptr<json>& item
405 : : ) const override;
406 : :
407 : : private:
408 : : bool looped { false };
409 : : bool touched { false };
410 : : std::vector<std::pair<std::string, std::shared_ptr<json>>> data;
411 : : std::unordered_map<std::string, size_t> indexes;
412 : : std::vector<std::string> keys {};
413 : :
414 : : static std::string format_item(
415 : : const std::pair<std::string, std::shared_ptr<json>>& item,
416 : : size_t nested_level, bool pretty
417 : : );
418 : : };
419 : :
420 : : template <typename Collection>
421 : : concept Iterable = requires(Collection c) {
422 : : { c.begin() } -> std::same_as<typename Collection::iterator>;
423 : : { c.end() } -> std::same_as<typename Collection::iterator>;
424 : : };
425 : :
426 : : template <typename Formatter, typename Element>
427 : : concept CallableFormatter
428 : : = requires(Formatter f, const Element& e, size_t level, bool pretty) {
429 : : { f(e, level, pretty) } -> std::convertible_to<std::string>;
430 : : };
431 : :
432 : : template <Iterable Collection, typename Formatter>
433 : : requires CallableFormatter<Formatter, typename Collection::value_type>
434 : 1573 : std::string format_container(
435 : : const Collection& elements, Formatter lambda, const size_t indent_level,
436 : : const bool pretty
437 : : ) {
438 : 1573 : std::string result;
439 : 1573 : std::string indent;
440 : 1573 : size_t nested_level = indent_level;
441 : :
442 [ + + ]: 1573 : if (pretty) {
443 : 6 : nested_level++;
444 [ + - ]: 6 : indent = std::string(indent_level, '\t');
445 [ + - ]: 6 : result += "\n";
446 : : }
447 : :
448 [ + + ]: 10207 : for (auto it = elements.begin(); it != elements.end(); ++it) {
449 [ + + ]: 8634 : if (it != elements.begin()) {
450 [ + - ]: 7065 : result += ',';
451 [ + + + - ]: 7065 : result += pretty ? "\n" : " ";
452 : : }
453 [ + + ]: 8634 : if (pretty) {
454 [ + - + - ]: 12 : result += indent + '\t';
455 : : }
456 [ + - + - ]: 8634 : result += lambda(*it, nested_level, pretty);
457 : : }
458 : :
459 [ + + ]: 1573 : if (pretty) {
460 [ + - + - ]: 6 : result += "\n" + indent;
461 : : }
462 : 3146 : return result;
463 : 1573 : }
464 : :
465 : : std::invalid_argument throw_message(
466 : : const std::shared_ptr<const json>& obj1,
467 : : const std::shared_ptr<const json>& obj2,
468 : : std::source_location location = std::source_location::current()
469 : : );
470 : : }
471 : : #endif // JSON_HPP
|