LCOV - code coverage report
Current view: top level - include - json.hpp (source / functions) Coverage Total Hit
Test: JSON Expression Parser Lines: 100.0 % 22 22
Test Date: 2024-11-04 20:34:32 Functions: 100.0 % 5 5
Branches: 68.8 % 32 22

             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
        

Generated by: LCOV version 2.0-1