From 6268287940ce3a2523fda959ed1efe7d5f0051c4 Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 17 Apr 2016 22:08:21 +0200 Subject: [PATCH] improved documentation and test coverage --- README.md | 2 +- doc/examples/flatten.cpp | 3 ++ doc/examples/flatten.link | 2 +- doc/examples/flatten.output | 1 + src/json.hpp | 58 ++++++++++++++++++++++++++++++------- src/json.hpp.re2c | 58 ++++++++++++++++++++++++++++++------- test/unit.cpp | 5 ++++ 7 files changed, 105 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index d39663ee..2dd60bf1 100644 --- a/README.md +++ b/README.md @@ -428,7 +428,7 @@ $ make $ ./json_unit "*" =============================================================================== -All tests passed (3344299 assertions in 29 test cases) +All tests passed (3344416 assertions in 30 test cases) ``` For more information, have a look at the file [.travis.yml](https://github.com/nlohmann/json/blob/master/.travis.yml). diff --git a/doc/examples/flatten.cpp b/doc/examples/flatten.cpp index 5d769202..0601f8a3 100644 --- a/doc/examples/flatten.cpp +++ b/doc/examples/flatten.cpp @@ -31,4 +31,7 @@ int main() // call flatten() std::cout << std::setw(4) << j.flatten() << '\n'; + + // flatten for a primitive value + std::cout << j["pi"].flatten() << '\n'; } diff --git a/doc/examples/flatten.link b/doc/examples/flatten.link index 70ba78ba..0fe78bbb 100644 --- a/doc/examples/flatten.link +++ b/doc/examples/flatten.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/flatten.output b/doc/examples/flatten.output index beb368fa..fedfc8ef 100644 --- a/doc/examples/flatten.output +++ b/doc/examples/flatten.output @@ -14,3 +14,4 @@ "/object/~1": "slash", "/pi": 3.141 } +{"":3.141} diff --git a/src/json.hpp b/src/json.hpp index 47046c03..030c8f2c 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -3273,8 +3273,8 @@ class basic_json @return reference to the element at index @a idx - @throw std::domain_error if JSON is not an array or null; example: `"cannot - use operator[] with string"` + @throw std::domain_error if JSON is not an array or null; example: + `"cannot use operator[] with string"` @complexity Constant if @a idx is in the range of the array. Otherwise linear in `idx - size()`. @@ -3620,7 +3620,9 @@ class basic_json @complexity Linear in the length of the JSON pointer. - @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number @liveexample{The behavior is shown in the example.,operatorjson_pointer} @@ -3645,8 +3647,9 @@ class basic_json @complexity Linear in the length of the JSON pointer. - @throw std::out_of_range if the JSON pointer can not be resolved - @throw std::out_of_range if the special value `-` is used for an array + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number @liveexample{The behavior is shown in the example., operatorjson_pointer_const} @@ -8923,9 +8926,12 @@ basic_json_parser_63: empty string is assumed which references the whole JSON value - @throw std::domain_error if reference token is nonempty and does not - begin with a slash (`/`), or if a tilde (`~`) is not followed - by `0` (representing `~`) or `1` (representing `/`). + @throw std::domain_error if reference token is nonempty and does not + begin with a slash (`/`); example: `"JSON pointer must be empty or + begin with /"` + @throw std::domain_error if a tilde (`~`) is not followed by `0` + (representing `~`) or `1` (representing `/`); example: `"escape error: + ~ must be followed with 0 or 1"` @liveexample{The example shows the construction several valid JSON pointers as well as the exceptional behavior.,json_pointer} @@ -8944,6 +8950,8 @@ basic_json_parser_63: { pointer result = &j; + // in case no reference tokens exist, return a reference to the + // JSON value j which will be overwritten by a primitive value for (const auto& reference_token : reference_tokens) { switch (result->m_type) @@ -8952,10 +8960,12 @@ basic_json_parser_63: { if (reference_token == "0") { + // start a new array if reference token is 0 result = &result->operator[](0); } else { + // start a new object otherwise result = &result->operator[](reference_token); } break; @@ -8963,19 +8973,38 @@ basic_json_parser_63: case value_t::object: { + // create an entry in the object result = &result->operator[](reference_token); break; } case value_t::array: { + // create an entry in the array result = &result->operator[](static_cast(std::stoi(reference_token))); break; } + /* + This function is only to be called from the unflatten() + function. There, j is initially of type null. + + - In case the reference tokens are empty, a reference to + j is returned and overwritten by the desired value by + the unflatten() function. + - If there are reference tokens, the null value of j will + be changed to an object or array after reading the first + reference token. + - All subsequent tokens work on arrays or objects and will + not change the type of j. + + Consequently, the type of @a j will always be null, + object, or array. Hence, the following line is + unreachable. + */ default: { - throw std::domain_error("unresolved reference token '" + reference_token + "'"); + break; // LCOV_EXCL_LINE } } } @@ -9361,7 +9390,11 @@ basic_json_parser_63: throw std::domain_error("values in object must be primitive"); } - // assign value to reference pointed to by JSON pointer + // assign value to reference pointed to by JSON pointer; + // Note that if the JSON pointer is "" (i.e., points to the + // whole value), function get_and_create returns a reference + // to result itself. An assignment will then create a + // primitive value. json_pointer(element.first).get_and_create(result) = element.second; } @@ -9390,7 +9423,8 @@ basic_json_parser_63: @return an object that maps JSON pointers to primitve values - @note Empty objects and arrays are flattened to `null`. + @note Empty objects and arrays are flattened to `null` and will not be + reconstructed correctly by the @ref unflatten() function. @complexity Linear in the size the JSON value. @@ -9428,6 +9462,8 @@ basic_json_parser_63: @complexity Linear in the size the JSON value. + @throws std::domain_error + @liveexample{The following code shows how a flattened JSON object is unflattened into the original nested JSON object.,unflatten} diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index ac11f08a..f5fbe65b 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -3273,8 +3273,8 @@ class basic_json @return reference to the element at index @a idx - @throw std::domain_error if JSON is not an array or null; example: `"cannot - use operator[] with string"` + @throw std::domain_error if JSON is not an array or null; example: + `"cannot use operator[] with string"` @complexity Constant if @a idx is in the range of the array. Otherwise linear in `idx - size()`. @@ -3620,7 +3620,9 @@ class basic_json @complexity Linear in the length of the JSON pointer. - @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number @liveexample{The behavior is shown in the example.,operatorjson_pointer} @@ -3645,8 +3647,9 @@ class basic_json @complexity Linear in the length of the JSON pointer. - @throw std::out_of_range if the JSON pointer can not be resolved - @throw std::out_of_range if the special value `-` is used for an array + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number @liveexample{The behavior is shown in the example., operatorjson_pointer_const} @@ -8233,9 +8236,12 @@ class basic_json empty string is assumed which references the whole JSON value - @throw std::domain_error if reference token is nonempty and does not - begin with a slash (`/`), or if a tilde (`~`) is not followed - by `0` (representing `~`) or `1` (representing `/`). + @throw std::domain_error if reference token is nonempty and does not + begin with a slash (`/`); example: `"JSON pointer must be empty or + begin with /"` + @throw std::domain_error if a tilde (`~`) is not followed by `0` + (representing `~`) or `1` (representing `/`); example: `"escape error: + ~ must be followed with 0 or 1"` @liveexample{The example shows the construction several valid JSON pointers as well as the exceptional behavior.,json_pointer} @@ -8254,6 +8260,8 @@ class basic_json { pointer result = &j; + // in case no reference tokens exist, return a reference to the + // JSON value j which will be overwritten by a primitive value for (const auto& reference_token : reference_tokens) { switch (result->m_type) @@ -8262,10 +8270,12 @@ class basic_json { if (reference_token == "0") { + // start a new array if reference token is 0 result = &result->operator[](0); } else { + // start a new object otherwise result = &result->operator[](reference_token); } break; @@ -8273,19 +8283,38 @@ class basic_json case value_t::object: { + // create an entry in the object result = &result->operator[](reference_token); break; } case value_t::array: { + // create an entry in the array result = &result->operator[](static_cast(std::stoi(reference_token))); break; } + /* + This function is only to be called from the unflatten() + function. There, j is initially of type null. + + - In case the reference tokens are empty, a reference to + j is returned and overwritten by the desired value by + the unflatten() function. + - If there are reference tokens, the null value of j will + be changed to an object or array after reading the first + reference token. + - All subsequent tokens work on arrays or objects and will + not change the type of j. + + Consequently, the type of @a j will always be null, + object, or array. Hence, the following line is + unreachable. + */ default: { - throw std::domain_error("unresolved reference token '" + reference_token + "'"); + break; // LCOV_EXCL_LINE } } } @@ -8671,7 +8700,11 @@ class basic_json throw std::domain_error("values in object must be primitive"); } - // assign value to reference pointed to by JSON pointer + // assign value to reference pointed to by JSON pointer; + // Note that if the JSON pointer is "" (i.e., points to the + // whole value), function get_and_create returns a reference + // to result itself. An assignment will then create a + // primitive value. json_pointer(element.first).get_and_create(result) = element.second; } @@ -8700,7 +8733,8 @@ class basic_json @return an object that maps JSON pointers to primitve values - @note Empty objects and arrays are flattened to `null`. + @note Empty objects and arrays are flattened to `null` and will not be + reconstructed correctly by the @ref unflatten() function. @complexity Linear in the size the JSON value. @@ -8738,6 +8772,8 @@ class basic_json @complexity Linear in the size the JSON value. + @throws std::domain_error + @liveexample{The following code shows how a flattened JSON object is unflattened into the original nested JSON object.,unflatten} diff --git a/test/unit.cpp b/test/unit.cpp index f79a29fd..f5a6fc09 100644 --- a/test/unit.cpp +++ b/test/unit.cpp @@ -12365,6 +12365,11 @@ TEST_CASE("JSON pointers") CHECK_THROWS_AS(json({{"/1", {1, 2, 3}}}).unflatten(), std::domain_error); CHECK_THROWS_WITH(json({{"/1", {1, 2, 3}}}).unflatten(), "values in object must be primitive"); + // error for conflicting values + json j_error = {{"", 42}, {"/foo", 17}}; + CHECK_THROWS_AS(j_error.unflatten(), std::domain_error); + CHECK_THROWS_WITH(j_error.unflatten(), "unresolved reference token 'foo'"); + // explicit roundtrip check CHECK(j.flatten().unflatten() == j);