diff --git a/doc/Doxyfile b/doc/Doxyfile index 8c7c9ade..d880366b 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -163,7 +163,7 @@ CHM_FILE = HHC_LOCATION = GENERATE_CHI = NO CHM_INDEX_ENCODING = -BINARY_TOC = YES +BINARY_TOC = NO TOC_EXPAND = NO GENERATE_QHP = NO QCH_FILE = @@ -176,7 +176,7 @@ QHG_LOCATION = GENERATE_ECLIPSEHELP = NO ECLIPSE_DOC_ID = org.doxygen.Project DISABLE_INDEX = NO -GENERATE_TREEVIEW = YES +GENERATE_TREEVIEW = NO ENUM_VALUES_PER_LINE = 4 TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO diff --git a/doc/Makefile b/doc/Makefile index 218116d6..20c65fe5 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -56,8 +56,6 @@ docset: create_output cp Doxyfile Doxyfile_docset gsed -i 's/DISABLE_INDEX = NO/DISABLE_INDEX = YES/' Doxyfile_docset gsed -i 's/SEARCHENGINE = YES/SEARCHENGINE = NO/' Doxyfile_docset - gsed -i 's/GENERATE_TREEVIEW = YES/GENERATE_TREEVIEW = NO/' Doxyfile_docset - gsed -i 's/BINARY_TOC = YES/BINARY_TOC = NO/' Doxyfile_docset gsed -i 's@HTML_EXTRA_STYLESHEET = css/mylayout.css@HTML_EXTRA_STYLESHEET = css/mylayout_docset.css@' Doxyfile_docset rm -fr html *.docset doxygen Doxyfile_docset diff --git a/doc/examples/at__object_t_key_type.cpp b/doc/examples/at__object_t_key_type.cpp new file mode 100644 index 00000000..85de5656 --- /dev/null +++ b/doc/examples/at__object_t_key_type.cpp @@ -0,0 +1,33 @@ +#include + +using namespace nlohmann; + +int main() +{ + // create JSON object + json object = + { + {"the good", "il buono"}, + {"the bad", "il cativo"}, + {"the ugly", "il brutto"} + }; + + // output element with key "the ugly" + std::cout << object.at("the ugly") << '\n'; + + // change element with key "the bad" + object.at("the bad") = "il cattivo"; + + // output changed array + std::cout << object << '\n'; + + // try to write at a nonexisting key + try + { + object.at("the fast") = "il rapido"; + } + catch (std::out_of_range) + { + std::cout << "out of range" << '\n'; + } +} diff --git a/doc/examples/at__object_t_key_type.output b/doc/examples/at__object_t_key_type.output new file mode 100644 index 00000000..90ccb1cd --- /dev/null +++ b/doc/examples/at__object_t_key_type.output @@ -0,0 +1,3 @@ +"il brutto" +{"the bad":"il cattivo","the good":"il buono","the ugly":"il brutto"} +out of range diff --git a/doc/examples/at__object_t_key_type_const.cpp b/doc/examples/at__object_t_key_type_const.cpp new file mode 100644 index 00000000..4a335527 --- /dev/null +++ b/doc/examples/at__object_t_key_type_const.cpp @@ -0,0 +1,27 @@ +#include + +using namespace nlohmann; + +int main() +{ + // create JSON object + json object = + { + {"the good", "il buono"}, + {"the bad", "il cativo"}, + {"the ugly", "il brutto"} + }; + + // output element with key "the ugly" + std::cout << object.at("the ugly") << '\n'; + + // try to read from a nonexisting key + try + { + std::cout << object.at("the fast") << '\n'; + } + catch (std::out_of_range) + { + std::cout << "out of range" << '\n'; + } +} diff --git a/doc/examples/at__object_t_key_type_const.output b/doc/examples/at__object_t_key_type_const.output new file mode 100644 index 00000000..b3dd11d3 --- /dev/null +++ b/doc/examples/at__object_t_key_type_const.output @@ -0,0 +1,2 @@ +"il brutto" +out of range diff --git a/doc/examples/at__size_type.cpp b/doc/examples/at__size_type.cpp new file mode 100644 index 00000000..5195005e --- /dev/null +++ b/doc/examples/at__size_type.cpp @@ -0,0 +1,28 @@ +#include + +using namespace nlohmann; + +int main() +{ + // create JSON array + json array = {"first", "2nd", "third", "fourth"}; + + // output element at index 2 (third element) + std::cout << array.at(2) << '\n'; + + // change element at index 1 (second element) to "second" + array.at(1) = "second"; + + // output changed array + std::cout << array << '\n'; + + // try to write beyond the array limit + try + { + array.at(5) = "sixth"; + } + catch (std::out_of_range) + { + std::cout << "out of range" << '\n'; + } +} diff --git a/doc/examples/at__size_type.output b/doc/examples/at__size_type.output new file mode 100644 index 00000000..93748f7f --- /dev/null +++ b/doc/examples/at__size_type.output @@ -0,0 +1,3 @@ +"third" +["first","second","third","fourth"] +out of range diff --git a/doc/examples/at__size_type_const.cpp b/doc/examples/at__size_type_const.cpp new file mode 100644 index 00000000..8c4324d4 --- /dev/null +++ b/doc/examples/at__size_type_const.cpp @@ -0,0 +1,22 @@ +#include + +using namespace nlohmann; + +int main() +{ + // create JSON array + json array = {"first", "2nd", "third", "fourth"}; + + // output element at index 2 (third element) + std::cout << array.at(2) << '\n'; + + // try to read beyond the array limit + try + { + std::cout << array.at(5) << '\n'; + } + catch (std::out_of_range) + { + std::cout << "out of range" << '\n'; + } +} diff --git a/doc/examples/at__size_type_const.output b/doc/examples/at__size_type_const.output new file mode 100644 index 00000000..d0b7a66a --- /dev/null +++ b/doc/examples/at__size_type_const.output @@ -0,0 +1,2 @@ +"third" +out of range diff --git a/doc/examples/operatorarray__size_type.cpp b/doc/examples/operatorarray__size_type.cpp new file mode 100644 index 00000000..70a7d562 --- /dev/null +++ b/doc/examples/operatorarray__size_type.cpp @@ -0,0 +1,24 @@ +#include + +using namespace nlohmann; + +int main() +{ + // create a JSON array + json array = {1, 2, 3, 4, 5}; + + // output element at index 3 (fourth element) + std::cout << array[3] << '\n'; + + // change last element to 6 + array[array.size() - 1] = 6; + + // output changed array + std::cout << array << '\n'; + + // write beyond array limit + array[10] = 11; + + // output changed array + std::cout << array << '\n'; +} diff --git a/doc/examples/operatorarray__size_type.output b/doc/examples/operatorarray__size_type.output new file mode 100644 index 00000000..a91a1069 --- /dev/null +++ b/doc/examples/operatorarray__size_type.output @@ -0,0 +1,3 @@ +4 +[1,2,3,4,6] +[1,2,3,4,6,null,null,null,null,null,11] diff --git a/doc/examples/operatorarray__size_type_const.cpp b/doc/examples/operatorarray__size_type_const.cpp new file mode 100644 index 00000000..d99d8c5d --- /dev/null +++ b/doc/examples/operatorarray__size_type_const.cpp @@ -0,0 +1,12 @@ +#include + +using namespace nlohmann; + +int main() +{ + // create JSON array + json array = {"first", "2nd", "third", "fourth"}; + + // output element at index 2 (third element) + std::cout << array.at(2) << '\n'; +} diff --git a/doc/examples/operatorarray__size_type_const.output b/doc/examples/operatorarray__size_type_const.output new file mode 100644 index 00000000..4450c9f0 --- /dev/null +++ b/doc/examples/operatorarray__size_type_const.output @@ -0,0 +1 @@ +"third" diff --git a/doc/examples/parse__istream__parser_callback_t.cpp b/doc/examples/parse__istream__parser_callback_t.cpp new file mode 100644 index 00000000..85805310 --- /dev/null +++ b/doc/examples/parse__istream__parser_callback_t.cpp @@ -0,0 +1,55 @@ +#include + +using namespace nlohmann; + +int main() +{ + // a JSON text + auto text = R"( + { + "Image": { + "Width": 800, + "Height": 600, + "Title": "View from 15th Floor", + "Thumbnail": { + "Url": "http://www.example.com/image/481989943", + "Height": 125, + "Width": 100 + }, + "Animated" : false, + "IDs": [116, 943, 234, 38793] + } + } + )"; + + // fill a stream with JSON text + std::stringstream ss; + ss << text; + + // parse and serialize JSON + json j_complete = json::parse(ss); + std::cout << std::setw(4) << j_complete << "\n\n"; + + + // define parser callback + json::parser_callback_t cb = [](int depth, json::parse_event_t event, json & parsed) + { + // skip object elements with key "Thumbnail" + if (event == json::parse_event_t::key and parsed == json("Thumbnail")) + { + return false; + } + else + { + return true; + } + }; + + // fill a stream with JSON text + ss.clear(); + ss << text; + + // parse (with callback) and serialize JSON + json j_filtered = json::parse(ss, cb); + std::cout << std::setw(4) << j_filtered << '\n'; +} \ No newline at end of file diff --git a/doc/examples/parse__istream__parser_callback_t.output b/doc/examples/parse__istream__parser_callback_t.output new file mode 100644 index 00000000..279a7ff7 --- /dev/null +++ b/doc/examples/parse__istream__parser_callback_t.output @@ -0,0 +1,34 @@ +{ + "Image": { + "Animated": false, + "Height": 600, + "IDs": [ + 116, + 943, + 234, + 38793 + ], + "Thumbnail": { + "Height": 125, + "Url": "http://www.example.com/image/481989943", + "Width": 100 + }, + "Title": "View from 15th Floor", + "Width": 800 + } +} + +{ + "Image": { + "Animated": false, + "Height": 600, + "IDs": [ + 116, + 943, + 234, + 38793 + ], + "Title": "View from 15th Floor", + "Width": 800 + } +} diff --git a/doc/examples/parse__string__parser_callback_t.cpp b/doc/examples/parse__string__parser_callback_t.cpp new file mode 100644 index 00000000..52fecbff --- /dev/null +++ b/doc/examples/parse__string__parser_callback_t.cpp @@ -0,0 +1,47 @@ +#include + +using namespace nlohmann; + +int main() +{ + // a JSON text + std::string text = R"( + { + "Image": { + "Width": 800, + "Height": 600, + "Title": "View from 15th Floor", + "Thumbnail": { + "Url": "http://www.example.com/image/481989943", + "Height": 125, + "Width": 100 + }, + "Animated" : false, + "IDs": [116, 943, 234, 38793] + } + } + )"; + + // parse and serialize JSON + json j_complete = json::parse(text); + std::cout << std::setw(4) << j_complete << "\n\n"; + + + // define parser callback + json::parser_callback_t cb = [](int depth, json::parse_event_t event, json & parsed) + { + // skip object elements with key "Thumbnail" + if (event == json::parse_event_t::key and parsed == json("Thumbnail")) + { + return false; + } + else + { + return true; + } + }; + + // parse (with callback) and serialize JSON + json j_filtered = json::parse(text, cb); + std::cout << std::setw(4) << j_filtered << '\n'; +} \ No newline at end of file diff --git a/doc/examples/parse__string__parser_callback_t.output b/doc/examples/parse__string__parser_callback_t.output new file mode 100644 index 00000000..279a7ff7 --- /dev/null +++ b/doc/examples/parse__string__parser_callback_t.output @@ -0,0 +1,34 @@ +{ + "Image": { + "Animated": false, + "Height": 600, + "IDs": [ + 116, + 943, + 234, + 38793 + ], + "Thumbnail": { + "Height": 125, + "Url": "http://www.example.com/image/481989943", + "Width": 100 + }, + "Title": "View from 15th Floor", + "Width": 800 + } +} + +{ + "Image": { + "Animated": false, + "Height": 600, + "IDs": [ + 116, + 943, + 234, + 38793 + ], + "Title": "View from 15th Floor", + "Width": 800 + } +} diff --git a/doc/examples/swap__array_t.cpp b/doc/examples/swap__array_t.cpp new file mode 100644 index 00000000..e9716183 --- /dev/null +++ b/doc/examples/swap__array_t.cpp @@ -0,0 +1,19 @@ +#include + +using namespace nlohmann; + +int main() +{ + // create a JSON value + json value = {{"array", {1, 2, 3, 4}}}; + + // create an array_t + json::array_t array = {"Snap", "Crackle", "Pop"}; + + // swap the array stored in the JSON value + value["array"].swap(array); + + // output the values + std::cout << "value = " << value << '\n'; + std::cout << "array = " << array << '\n'; +} diff --git a/doc/examples/swap__array_t.output b/doc/examples/swap__array_t.output new file mode 100644 index 00000000..365302cc --- /dev/null +++ b/doc/examples/swap__array_t.output @@ -0,0 +1,2 @@ +value = {"array":["Snap","Crackle","Pop"]} +array = [1,2,3,4] diff --git a/doc/examples/swap__reference.cpp b/doc/examples/swap__reference.cpp new file mode 100644 index 00000000..4c03cc01 --- /dev/null +++ b/doc/examples/swap__reference.cpp @@ -0,0 +1,17 @@ +#include + +using namespace nlohmann; + +int main() +{ + // create two JSON values + json j1 = {1, 2, 3, 4, 5}; + json j2 = {{"pi", 3.141592653589793}, {"e", 2.718281828459045}}; + + // swap the values + j1.swap(j2); + + // output the values + std::cout << "j1 = " << j1 << '\n'; + std::cout << "j2 = " << j2 << '\n'; +} diff --git a/doc/examples/swap__reference.output b/doc/examples/swap__reference.output new file mode 100644 index 00000000..f5495bd5 --- /dev/null +++ b/doc/examples/swap__reference.output @@ -0,0 +1,2 @@ +j1 = {"e":2.71828182845905,"pi":3.14159265358979} +j2 = [1,2,3,4,5] diff --git a/src/json.hpp b/src/json.hpp index d1977e49..ed4d24fd 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -336,20 +336,77 @@ class basic_json // JSON parser callback // ////////////////////////// - /// JSON callback event enumeration + /*! + @brief JSON callback events + + This enumeration lists the parser events that can trigger calling a + callback function of type @ref parser_callback_t during parsing. + */ enum class parse_event_t : uint8_t { - object_start, ///< start an object scope (found a '{' token) - object_end, ///< end of an object scope (found '}' token) - array_start, ///< start of an array scope (found '[' token) - array_end, ///< end of an array scope (found ']' token) - key, ///< found an object key within an object scope - value ///< a value in an appropriate context (i.e., following a tag in an object scope) + /// the parser read `{` and started to process a JSON object + object_start, + /// the parser read `}` and finished processing a JSON object + object_end, + /// the parser read `[` and started to process a JSON array + array_start, + /// the parser read `]` and finished processing a JSON array + array_end, + /// the parser read a key of a value in an object + key, + /// the parser finished reading a JSON value + value }; - /// per-element parser callback type - using parser_callback_t = std::function; + /*! + @brief per-element parser callback type + + With a parser callback function, the result of parsing a JSON text can be + influenced. When passed to @ref parse(std::istream&, parser_callback_t) or + @ref parse(const string_t&, parser_callback_t), it is called on certain + events (passed as @ref parse_event_t via parameter @a event) with a set + recursion depth @a depth and context JSON value @a parsed. The return value + of the callback function is a boolean indicating whether the element that + emitted the callback shall be kept or not. + + We distinguish six scenarios (determined by the event type) in which the + callback function can be called. The following table describes the values + of the parameters @a depth, @a event, and @a parsed. + + parameter @a event | description | parameter @a depth | parameter @a parsed + ------------------ | ----------- | ------------------ | ------------------- + parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded + parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key + parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object + parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded + parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array + parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value + + Discarding a value (i.e., returning `false`) has different effects depending on the + context in which function was called: + + - Discarded values in structured types are skipped. That is, the parser + will behave as if the discarded value was never read. + - In case a value outside a structured type is skipped, it is replaced with + `null`. This case happens if the top-level element is skipped. + + @param[in] depth the depth of the recursion during parsing + + @param[in] event an event of type parse_event_t indicating the context in + the callback function has been called + + @param[in,out] parsed the current intermediate parse result; note that + writing to this value has no effect for parse_event_t::key events + + @return Whether the JSON value which called the function during parsing + should be kept (`true`) or not (`false`). In the latter case, it is either + skipped completely or replaced by an empty discarded object. + + @sa @ref parse(std::istream&, parser_callback_t) or + @ref parse(const string_t&, parser_callback_t) for examples + */ + using parser_callback_t = std::function; /*! @brief comparison operator for JSON value types @@ -1760,7 +1817,25 @@ class basic_json /// @name element access /// @{ - /// access specified element with bounds checking + /*! + @brief access specified array element with bounds checking + + Returns a reference to the element at specified location @a idx, with + bounds checking. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw std::domain_error if JSON is not an array + @throw std::out_of_range if the index @a idx is out of range of the array; + that is, `idx >= size()` + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read and + written using at.,at__size_type} + */ reference at(size_type idx) { // at only works for arrays @@ -1772,7 +1847,25 @@ class basic_json return m_value.array->at(idx); } - /// access specified element with bounds checking + /*! + @brief access specified array element with bounds checking + + Returns a const reference to the element at specified location @a idx, with + bounds checking. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw std::domain_error if JSON is not an array + @throw std::out_of_range if the index @a idx is out of range of the array; + that is, `idx >= size()` + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + at.,at__size_type_const} + */ const_reference at(size_type idx) const { // at only works for arrays @@ -1784,7 +1877,25 @@ class basic_json return m_value.array->at(idx); } - /// access specified element with bounds checking + /*! + @brief access specified object element with bounds checking + + Returns a reference to the element at with specified key @a key, with + bounds checking. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw std::domain_error if JSON is not an object + @throw std::out_of_range if the key @a key is is not stored in the object; + that is, `find(key) == end()` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using at.,at__object_t_key_type} + */ reference at(const typename object_t::key_type& key) { // at only works for objects @@ -1796,7 +1907,25 @@ class basic_json return m_value.object->at(key); } - /// access specified element with bounds checking + /*! + @brief access specified object element with bounds checking + + Returns a const reference to the element at with specified key @a key, with + bounds checking. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw std::domain_error if JSON is not an object + @throw std::out_of_range if the key @a key is is not stored in the object; + that is, `find(key) == end()` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + at.,at__object_t_key_type_const} + */ const_reference at(const typename object_t::key_type& key) const { // at only works for objects @@ -1808,7 +1937,28 @@ class basic_json return m_value.object->at(key); } - /// access specified element + /*! + @brief access specified array element + + Returns a reference to the element at specified location @a idx. + + @note If @a idx is beyond the range of the array (i.e., `idx >= size()`), + then the array is silently filled up with `null` values to make `idx` a + valid reference to the last stored element. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw std::domain_error if JSON is not an array or null + + @complexity Constant if @a idx is in the range of the array. Otherwise + linear in `idx - size()`. + + @liveexample{The example below shows how array elements can be read and + written using [] operator. Note the addition of `null` + values.,operatorarray__size_type} + */ reference operator[](size_type idx) { // implicitly convert null to object @@ -1834,7 +1984,22 @@ class basic_json return m_value.array->operator[](idx); } - /// access specified element + /*! + @brief access specified array element + + Returns a const reference to the element at specified location @a idx. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw std::domain_error if JSON is not an array + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + the [] operator.,operatorarray__size_type_const} + */ const_reference operator[](size_type idx) const { // at only works for arrays @@ -2750,6 +2915,19 @@ class basic_json /*! @brief exchanges the values + + Exchanges the contents of the JSON value with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other JSON value to exchange the contents with + + @complexity Constant. + + @liveexample{The example below shows how JSON arrays can be + swapped.,swap__reference} + @ingroup container */ void swap(reference other) noexcept ( @@ -2763,7 +2941,25 @@ class basic_json std::swap(m_value, other.m_value); } - /// swaps the contents + /*! + @brief exchanges the values + + Exchanges the contents of a JSON array with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other array to exchange the contents with + + @throw std::domain_error when JSON value is not an array + + @complexity Constant. + + @liveexample{The example below shows how JSON values can be + swapped.,swap__array_t} + + @ingroup container + */ void swap(array_t& other) { // swap only works for arrays @@ -3082,8 +3278,9 @@ class basic_json @brief deserialize from string @param[in] s string to read a serialized JSON value from - @param[in] cb a parser callback function of type parser_callback_t which is - used to control the deserialization by filtering unwanted values (optional) + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) @return result of the deserialization @@ -3091,7 +3288,8 @@ class basic_json LL(1) parser. The complexity can be higher if the parser callback function @a cb has a super-linear complexity. - @todo Add example. + @liveexample{The example below demonstrates the parse function with and + without callback function.,parse__string__parser_callback_t} @sa parse(std::istream&, parser_callback_t) for a version that reads from an input stream @@ -3105,8 +3303,9 @@ class basic_json @brief deserialize from stream @param[in,out] i stream to read a serialized JSON value from - @param[in] cb a parser callback function of type parser_callback_t which is - used to control the deserialization by filtering unwanted values (optional) + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) @return result of the deserialization @@ -3114,7 +3313,8 @@ class basic_json LL(1) parser. The complexity can be higher if the parser callback function @a cb has a super-linear complexity. - @todo Add example. + @liveexample{The example below demonstrates the parse function with and + without callback function.,parse__istream__parser_callback_t} @sa parse(const string_t&, parser_callback_t) for a version that reads from a string @@ -5710,7 +5910,9 @@ basic_json_parser_59: expect(lexer::token_type::end_of_input); - return result; + // return parser result and replace it with null in case the + // top-level value was discarded by the callback function + return result.is_discarded() ? basic_json() : result; } private: @@ -5763,7 +5965,15 @@ basic_json_parser_59: bool keep_tag = false; if (keep) { - keep_tag = callback ? callback(depth, parse_event_t::key, basic_json(key)) : true; + if (callback) + { + basic_json k(key); + keep_tag = callback(depth, parse_event_t::key, k); + } + else + { + keep_tag = true; + } } // parse separator (:) diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index 6b5b760c..4c574e6f 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -336,20 +336,77 @@ class basic_json // JSON parser callback // ////////////////////////// - /// JSON callback event enumeration + /*! + @brief JSON callback events + + This enumeration lists the parser events that can trigger calling a + callback function of type @ref parser_callback_t during parsing. + */ enum class parse_event_t : uint8_t { - object_start, ///< start an object scope (found a '{' token) - object_end, ///< end of an object scope (found '}' token) - array_start, ///< start of an array scope (found '[' token) - array_end, ///< end of an array scope (found ']' token) - key, ///< found an object key within an object scope - value ///< a value in an appropriate context (i.e., following a tag in an object scope) + /// the parser read `{` and started to process a JSON object + object_start, + /// the parser read `}` and finished processing a JSON object + object_end, + /// the parser read `[` and started to process a JSON array + array_start, + /// the parser read `]` and finished processing a JSON array + array_end, + /// the parser read a key of a value in an object + key, + /// the parser finished reading a JSON value + value }; - /// per-element parser callback type - using parser_callback_t = std::function; + /*! + @brief per-element parser callback type + + With a parser callback function, the result of parsing a JSON text can be + influenced. When passed to @ref parse(std::istream&, parser_callback_t) or + @ref parse(const string_t&, parser_callback_t), it is called on certain + events (passed as @ref parse_event_t via parameter @a event) with a set + recursion depth @a depth and context JSON value @a parsed. The return value + of the callback function is a boolean indicating whether the element that + emitted the callback shall be kept or not. + + We distinguish six scenarios (determined by the event type) in which the + callback function can be called. The following table describes the values + of the parameters @a depth, @a event, and @a parsed. + + parameter @a event | description | parameter @a depth | parameter @a parsed + ------------------ | ----------- | ------------------ | ------------------- + parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded + parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key + parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object + parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded + parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array + parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value + + Discarding a value (i.e., returning `false`) has different effects depending on the + context in which function was called: + + - Discarded values in structured types are skipped. That is, the parser + will behave as if the discarded value was never read. + - In case a value outside a structured type is skipped, it is replaced with + `null`. This case happens if the top-level element is skipped. + + @param[in] depth the depth of the recursion during parsing + + @param[in] event an event of type parse_event_t indicating the context in + the callback function has been called + + @param[in,out] parsed the current intermediate parse result; note that + writing to this value has no effect for parse_event_t::key events + + @return Whether the JSON value which called the function during parsing + should be kept (`true`) or not (`false`). In the latter case, it is either + skipped completely or replaced by an empty discarded object. + + @sa @ref parse(std::istream&, parser_callback_t) or + @ref parse(const string_t&, parser_callback_t) for examples + */ + using parser_callback_t = std::function; /*! @brief comparison operator for JSON value types @@ -1760,7 +1817,25 @@ class basic_json /// @name element access /// @{ - /// access specified element with bounds checking + /*! + @brief access specified array element with bounds checking + + Returns a reference to the element at specified location @a idx, with + bounds checking. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw std::domain_error if JSON is not an array + @throw std::out_of_range if the index @a idx is out of range of the array; + that is, `idx >= size()` + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read and + written using at.,at__size_type} + */ reference at(size_type idx) { // at only works for arrays @@ -1772,7 +1847,25 @@ class basic_json return m_value.array->at(idx); } - /// access specified element with bounds checking + /*! + @brief access specified array element with bounds checking + + Returns a const reference to the element at specified location @a idx, with + bounds checking. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw std::domain_error if JSON is not an array + @throw std::out_of_range if the index @a idx is out of range of the array; + that is, `idx >= size()` + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + at.,at__size_type_const} + */ const_reference at(size_type idx) const { // at only works for arrays @@ -1784,7 +1877,25 @@ class basic_json return m_value.array->at(idx); } - /// access specified element with bounds checking + /*! + @brief access specified object element with bounds checking + + Returns a reference to the element at with specified key @a key, with + bounds checking. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw std::domain_error if JSON is not an object + @throw std::out_of_range if the key @a key is is not stored in the object; + that is, `find(key) == end()` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using at.,at__object_t_key_type} + */ reference at(const typename object_t::key_type& key) { // at only works for objects @@ -1796,7 +1907,25 @@ class basic_json return m_value.object->at(key); } - /// access specified element with bounds checking + /*! + @brief access specified object element with bounds checking + + Returns a const reference to the element at with specified key @a key, with + bounds checking. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw std::domain_error if JSON is not an object + @throw std::out_of_range if the key @a key is is not stored in the object; + that is, `find(key) == end()` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + at.,at__object_t_key_type_const} + */ const_reference at(const typename object_t::key_type& key) const { // at only works for objects @@ -1808,7 +1937,28 @@ class basic_json return m_value.object->at(key); } - /// access specified element + /*! + @brief access specified array element + + Returns a reference to the element at specified location @a idx. + + @note If @a idx is beyond the range of the array (i.e., `idx >= size()`), + then the array is silently filled up with `null` values to make `idx` a + valid reference to the last stored element. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw std::domain_error if JSON is not an array or null + + @complexity Constant if @a idx is in the range of the array. Otherwise + linear in `idx - size()`. + + @liveexample{The example below shows how array elements can be read and + written using [] operator. Note the addition of `null` + values.,operatorarray__size_type} + */ reference operator[](size_type idx) { // implicitly convert null to object @@ -1834,7 +1984,22 @@ class basic_json return m_value.array->operator[](idx); } - /// access specified element + /*! + @brief access specified array element + + Returns a const reference to the element at specified location @a idx. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw std::domain_error if JSON is not an array + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + the [] operator.,operatorarray__size_type_const} + */ const_reference operator[](size_type idx) const { // at only works for arrays @@ -2750,6 +2915,19 @@ class basic_json /*! @brief exchanges the values + + Exchanges the contents of the JSON value with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other JSON value to exchange the contents with + + @complexity Constant. + + @liveexample{The example below shows how JSON arrays can be + swapped.,swap__reference} + @ingroup container */ void swap(reference other) noexcept ( @@ -2763,7 +2941,25 @@ class basic_json std::swap(m_value, other.m_value); } - /// swaps the contents + /*! + @brief exchanges the values + + Exchanges the contents of a JSON array with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other array to exchange the contents with + + @throw std::domain_error when JSON value is not an array + + @complexity Constant. + + @liveexample{The example below shows how JSON values can be + swapped.,swap__array_t} + + @ingroup container + */ void swap(array_t& other) { // swap only works for arrays @@ -3082,8 +3278,9 @@ class basic_json @brief deserialize from string @param[in] s string to read a serialized JSON value from - @param[in] cb a parser callback function of type parser_callback_t which is - used to control the deserialization by filtering unwanted values (optional) + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) @return result of the deserialization @@ -3091,7 +3288,8 @@ class basic_json LL(1) parser. The complexity can be higher if the parser callback function @a cb has a super-linear complexity. - @todo Add example. + @liveexample{The example below demonstrates the parse function with and + without callback function.,parse__string__parser_callback_t} @sa parse(std::istream&, parser_callback_t) for a version that reads from an input stream @@ -3105,8 +3303,9 @@ class basic_json @brief deserialize from stream @param[in,out] i stream to read a serialized JSON value from - @param[in] cb a parser callback function of type parser_callback_t which is - used to control the deserialization by filtering unwanted values (optional) + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) @return result of the deserialization @@ -3114,7 +3313,8 @@ class basic_json LL(1) parser. The complexity can be higher if the parser callback function @a cb has a super-linear complexity. - @todo Add example. + @liveexample{The example below demonstrates the parse function with and + without callback function.,parse__istream__parser_callback_t} @sa parse(const string_t&, parser_callback_t) for a version that reads from a string @@ -5016,7 +5216,9 @@ class basic_json expect(lexer::token_type::end_of_input); - return result; + // return parser result and replace it with null in case the + // top-level value was discarded by the callback function + return result.is_discarded() ? basic_json() : result; } private: @@ -5069,7 +5271,15 @@ class basic_json bool keep_tag = false; if (keep) { - keep_tag = callback ? callback(depth, parse_event_t::key, basic_json(key)) : true; + if (callback) + { + basic_json k(key); + keep_tag = callback(depth, parse_event_t::key, k); + } + else + { + keep_tag = true; + } } // parse separator (:) diff --git a/test/unit.cpp b/test/unit.cpp index 584da86c..c8074b34 100644 --- a/test/unit.cpp +++ b/test/unit.cpp @@ -7742,14 +7742,16 @@ TEST_CASE("parser class") return false; }); - CHECK (j_object.is_discarded()); + // the top-level object will be discarded, leaving a null + CHECK (j_object.is_null()); json j_array = json::parse(s_array, [](int, json::parse_event_t, const json&) { return false; }); - CHECK (j_array.is_discarded()); + // the top-level array will be discarded, leaving a null + CHECK (j_array.is_null()); } SECTION("filter specific element") @@ -7791,8 +7793,10 @@ TEST_CASE("parser class") { json j_object = json::parse(s_object, [](int, json::parse_event_t e, const json&) { - if (e == json::parse_event_t::object_end) + static bool first = true; + if (e == json::parse_event_t::object_end and first) { + first = false; return false; } else @@ -7801,14 +7805,17 @@ TEST_CASE("parser class") } }); - CHECK (j_object.is_discarded()); + // the first completed object will be discarded + CHECK (j_object == json({{"foo", 2}})); } { json j_array = json::parse(s_array, [](int, json::parse_event_t e, const json&) { - if (e == json::parse_event_t::array_end) + static bool first = true; + if (e == json::parse_event_t::array_end and first) { + first = false; return false; } else @@ -7817,7 +7824,8 @@ TEST_CASE("parser class") } }); - CHECK (j_array.is_discarded()); + // the first completed array will be discarded + CHECK (j_array == json({1, 2, 4, 5})); } } }