From d1e13d51281b6caf008869845792d77f72175a11 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 2 Aug 2017 22:12:41 +0200 Subject: [PATCH 1/4] :tada: first draft for #661 --- src/json.hpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/json.hpp b/src/json.hpp index c22cce4f..5253a613 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -12066,6 +12066,31 @@ class basic_json m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); } + void update(const_reference j) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(305, "cannot use merge with " + type_name())); + } + if (JSON_UNLIKELY(not j.is_object())) + { + JSON_THROW(type_error::create(305, "cannot use merge with " + j.type_name())); + } + + for (auto it = j.begin(); it != j.end(); ++it) + { + m_value.object->emplace(it.key(), it.value()); + } + } + /*! @brief exchanges the values From d2c3592908fcea433f08481231de41b68fe929fd Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 2 Aug 2017 22:44:58 +0200 Subject: [PATCH 2/4] :white_check_mark: added test cases for update #661 --- src/json.hpp | 52 ++++++++++++++++++++++++++++++++++--- test/src/unit-modifiers.cpp | 49 +++++++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 4 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 5253a613..bd44237c 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -12066,6 +12066,18 @@ class basic_json m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); } + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from JSON object @a j and overwrites existing keys. + + @param[in] j JSON object to read values from + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @since version 3.0.0 + */ void update(const_reference j) { // implicitly convert null value to an empty object @@ -12078,16 +12090,50 @@ class basic_json if (JSON_UNLIKELY(not is_object())) { - JSON_THROW(type_error::create(305, "cannot use merge with " + type_name())); + JSON_THROW(type_error::create(305, "cannot use merge() with " + std::string(type_name()))); } if (JSON_UNLIKELY(not j.is_object())) { - JSON_THROW(type_error::create(305, "cannot use merge with " + j.type_name())); + JSON_THROW(type_error::create(305, "cannot use merge() with " + std::string(j.type_name()))); } for (auto it = j.begin(); it != j.end(); ++it) { - m_value.object->emplace(it.key(), it.value()); + m_value.object->operator[](it.key()) = it.value(); + } + } + + void update(const_iterator first, const_iterator last) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(305, "cannot use merge() with " + std::string(type_name()))); + } + + // check if range iterators belong to the same JSON object + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + // passed iterators must belong to objects + if (JSON_UNLIKELY(not first.m_object->is_object() + or not first.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } + + for (auto it = first; it != last; ++it) + { + m_value.object->operator[](it.key()) = it.value(); } } diff --git a/test/src/unit-modifiers.cpp b/test/src/unit-modifiers.cpp index eeb70a0c..64ac0b03 100644 --- a/test/src/unit-modifiers.cpp +++ b/test/src/unit-modifiers.cpp @@ -489,7 +489,7 @@ TEST_CASE("modifiers") } } - SECTION("insert") + SECTION("insert()") { json j_array = {1, 2, 3, 4}; json j_value = 5; @@ -740,6 +740,53 @@ TEST_CASE("modifiers") } } + SECTION("update()") + { + json j_object1 = {{"one", "eins"}, {"two", "zwei"}}; + json j_object2 = {{"three", "drei"}, {"two", "zwo"}}; + json j_array = {1, 2, 3, 4}; + + SECTION("const reference") + { + SECTION("proper usage") + { + j_object1.update(j_object2); + CHECK(j_object1 == json({{"one", "eins"}, {"two", "zwo"}, {"three", "drei"}})); + } + } + + SECTION("iterator range") + { + SECTION("proper usage") + { + j_object1.update(j_object2.begin(), j_object2.end()); + CHECK(j_object1 == json({{"one", "eins"}, {"two", "zwo"}, {"three", "drei"}})); + } + + SECTION("empty range") + { + j_object1.update(j_object2.begin(), j_object2.begin()); + CHECK(j_object1 == json({{"one", "eins"}, {"two", "zwei"}})); + } + + SECTION("invalid iterators") + { + json j_other_array2 = {"first", "second"}; + + CHECK_THROWS_AS(j_array.update(j_object2.begin(), j_object2.end()), json::type_error&); + CHECK_THROWS_AS(j_object1.update(j_object1.begin(), j_object2.end()), json::invalid_iterator&); + CHECK_THROWS_AS(j_object1.update(j_array.begin(), j_array.end()), json::invalid_iterator&); + + CHECK_THROWS_WITH(j_array.update(j_object2.begin(), j_object2.end()), + "[json.exception.type_error.305] cannot use merge() with array"); + CHECK_THROWS_WITH(j_object1.update(j_object1.begin(), j_object2.end()), + "[json.exception.invalid_iterator.210] iterators do not fit"); + CHECK_THROWS_WITH(j_object1.update(j_array.begin(), j_array.end()), + "[json.exception.invalid_iterator.202] iterators first and last must point to objects"); + } + } + } + SECTION("swap()") { SECTION("json") From e523312fa29d3615872ea6cadca4bfa0715f56c5 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 3 Aug 2017 17:47:42 +0200 Subject: [PATCH 3/4] :white_check_mark: improved test coverage --- test/src/unit-modifiers.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/src/unit-modifiers.cpp b/test/src/unit-modifiers.cpp index 64ac0b03..107d0575 100644 --- a/test/src/unit-modifiers.cpp +++ b/test/src/unit-modifiers.cpp @@ -752,6 +752,19 @@ TEST_CASE("modifiers") { j_object1.update(j_object2); CHECK(j_object1 == json({{"one", "eins"}, {"two", "zwo"}, {"three", "drei"}})); + + json j_null; + j_null.update(j_object2); + CHECK(j_null == j_object2); + } + + SECTION("wrong types") + { + CHECK_THROWS_AS(j_array.update(j_object1), json::type_error&); + CHECK_THROWS_WITH(j_array.update(j_object1), "[json.exception.type_error.305] cannot use merge() with array"); + + CHECK_THROWS_AS(j_object1.update(j_array), json::type_error&); + CHECK_THROWS_WITH(j_object1.update(j_array), "[json.exception.type_error.305] cannot use merge() with array"); } } @@ -761,6 +774,10 @@ TEST_CASE("modifiers") { j_object1.update(j_object2.begin(), j_object2.end()); CHECK(j_object1 == json({{"one", "eins"}, {"two", "zwo"}, {"three", "drei"}})); + + json j_null; + j_null.update(j_object2.begin(), j_object2.end()); + CHECK(j_null == j_object2); } SECTION("empty range") From 72afe53fa0f717b65bf62d81c2e2f409d1cfa3ad Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 15 Aug 2017 21:42:50 +0200 Subject: [PATCH 4/4] :memo: updated documentation for update() function #661 --- doc/examples/update.cpp | 17 +++++++++++++ doc/examples/update.link | 1 + doc/examples/update.output | 5 ++++ doc/examples/update__range.cpp | 17 +++++++++++++ doc/examples/update__range.link | 1 + doc/examples/update__range.output | 5 ++++ src/json.hpp | 40 ++++++++++++++++++++++++++++--- test/src/unit-modifiers.cpp | 6 ++--- 8 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 doc/examples/update.cpp create mode 100644 doc/examples/update.link create mode 100644 doc/examples/update.output create mode 100644 doc/examples/update__range.cpp create mode 100644 doc/examples/update__range.link create mode 100644 doc/examples/update__range.output diff --git a/doc/examples/update.cpp b/doc/examples/update.cpp new file mode 100644 index 00000000..4383df7f --- /dev/null +++ b/doc/examples/update.cpp @@ -0,0 +1,17 @@ +#include +#include "json.hpp" + +using json = nlohmann::json; + +int main() +{ + // create two JSON objects + json o1 = R"( {"color": "red", "price": 17.99} )"_json; + json o2 = R"( {"color": "blue", "speed": 100} )"_json; + + // add all keys from o2 to o1 (updating "color") + o1.update(o2); + + // output updated object o1 + std::cout << std::setw(2) << o1 << '\n'; +} diff --git a/doc/examples/update.link b/doc/examples/update.link new file mode 100644 index 00000000..46af9cbc --- /dev/null +++ b/doc/examples/update.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/update.output b/doc/examples/update.output new file mode 100644 index 00000000..69f0965a --- /dev/null +++ b/doc/examples/update.output @@ -0,0 +1,5 @@ +{ + "color": "blue", + "price": 17.99, + "speed": 100 +} diff --git a/doc/examples/update__range.cpp b/doc/examples/update__range.cpp new file mode 100644 index 00000000..7c56aad3 --- /dev/null +++ b/doc/examples/update__range.cpp @@ -0,0 +1,17 @@ +#include +#include "json.hpp" + +using json = nlohmann::json; + +int main() +{ + // create two JSON objects + json o1 = R"( {"color": "red", "price": 17.99} )"_json; + json o2 = R"( {"color": "blue", "speed": 100} )"_json; + + // add all keys from o2 to o1 (updating "color") + o1.update(o2.begin(), o2.end()); + + // output updated object o1 + std::cout << std::setw(2) << o1 << '\n'; +} diff --git a/doc/examples/update__range.link b/doc/examples/update__range.link new file mode 100644 index 00000000..1e90f66c --- /dev/null +++ b/doc/examples/update__range.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/update__range.output b/doc/examples/update__range.output new file mode 100644 index 00000000..69f0965a --- /dev/null +++ b/doc/examples/update__range.output @@ -0,0 +1,5 @@ +{ + "color": "blue", + "price": 17.99, + "speed": 100 +} diff --git a/src/json.hpp b/src/json.hpp index fc936fae..6485cdb7 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -323,6 +323,7 @@ json.exception.type_error.308 | cannot use push_back() with string | The @ref pu json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. +json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types. json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. @@ -11871,9 +11872,16 @@ class basic_json @param[in] j JSON object to read values from + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + @complexity O(N*log(size() + N)), where N is the number of elements to insert. + @liveexample{The example shows how `update()` is used.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + @since version 3.0.0 */ void update(const_reference j) @@ -11888,11 +11896,11 @@ class basic_json if (JSON_UNLIKELY(not is_object())) { - JSON_THROW(type_error::create(305, "cannot use merge() with " + std::string(type_name()))); + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); } if (JSON_UNLIKELY(not j.is_object())) { - JSON_THROW(type_error::create(305, "cannot use merge() with " + std::string(j.type_name()))); + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name()))); } for (auto it = j.begin(); it != j.end(); ++it) @@ -11901,6 +11909,32 @@ class basic_json } } + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from from range `[first, last)` and overwrites existing + keys. + + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + @throw invalid_iterator.202 if iterator @a first or @a last does does not + point to an object; example: `"iterators first and last must point to + objects"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used__range.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ void update(const_iterator first, const_iterator last) { // implicitly convert null value to an empty object @@ -11913,7 +11947,7 @@ class basic_json if (JSON_UNLIKELY(not is_object())) { - JSON_THROW(type_error::create(305, "cannot use merge() with " + std::string(type_name()))); + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); } // check if range iterators belong to the same JSON object diff --git a/test/src/unit-modifiers.cpp b/test/src/unit-modifiers.cpp index 107d0575..7db4153b 100644 --- a/test/src/unit-modifiers.cpp +++ b/test/src/unit-modifiers.cpp @@ -761,10 +761,10 @@ TEST_CASE("modifiers") SECTION("wrong types") { CHECK_THROWS_AS(j_array.update(j_object1), json::type_error&); - CHECK_THROWS_WITH(j_array.update(j_object1), "[json.exception.type_error.305] cannot use merge() with array"); + CHECK_THROWS_WITH(j_array.update(j_object1), "[json.exception.type_error.312] cannot use update() with array"); CHECK_THROWS_AS(j_object1.update(j_array), json::type_error&); - CHECK_THROWS_WITH(j_object1.update(j_array), "[json.exception.type_error.305] cannot use merge() with array"); + CHECK_THROWS_WITH(j_object1.update(j_array), "[json.exception.type_error.312] cannot use update() with array"); } } @@ -795,7 +795,7 @@ TEST_CASE("modifiers") CHECK_THROWS_AS(j_object1.update(j_array.begin(), j_array.end()), json::invalid_iterator&); CHECK_THROWS_WITH(j_array.update(j_object2.begin(), j_object2.end()), - "[json.exception.type_error.305] cannot use merge() with array"); + "[json.exception.type_error.312] cannot use update() with array"); CHECK_THROWS_WITH(j_object1.update(j_object1.begin(), j_object2.end()), "[json.exception.invalid_iterator.210] iterators do not fit"); CHECK_THROWS_WITH(j_object1.update(j_array.begin(), j_array.end()),