From 56efdc0f2798efa6c46601f1d4ac5300a5c26c89 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sat, 9 Oct 2021 15:27:28 +0200 Subject: [PATCH 1/4] :sparkles: add recursive update function --- doc/examples/update.cpp | 13 ++- doc/examples/update.output | 12 +++ doc/mkdocs/docs/api/basic_json/update.md | 66 ++++++++++++- include/nlohmann/json.hpp | 16 +++- single_include/nlohmann/json.hpp | 11 ++- test/src/unit-diagnostics.cpp | 9 +- test/src/unit-modifiers.cpp | 113 ++++++++++++++--------- 7 files changed, 184 insertions(+), 56 deletions(-) diff --git a/doc/examples/update.cpp b/doc/examples/update.cpp index fecdae7608..8f8b3bf8e0 100644 --- a/doc/examples/update.cpp +++ b/doc/examples/update.cpp @@ -7,12 +7,17 @@ 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; + json o1 = R"( {"color": "red", "price": 17.99, "names": {"de": "Flugzeug"}} )"_json; + json o2 = R"( {"color": "blue", "speed": 100, "names": {"en": "plane"}} )"_json; + json o3 = o1; - // add all keys from o2 to o1 (updating "color") + // add all keys from o2 to o1 (updating "color", replacing "names") o1.update(o2); - // output updated object o1 + // add all keys from o2 to o1 (updating "color", merging "names") + o3.update(o2, true); + + // output updated object o1 and o3 std::cout << std::setw(2) << o1 << '\n'; + std::cout << std::setw(2) << o3 << '\n'; } diff --git a/doc/examples/update.output b/doc/examples/update.output index 69f0965af7..c35a74513a 100644 --- a/doc/examples/update.output +++ b/doc/examples/update.output @@ -1,5 +1,17 @@ { "color": "blue", + "names": { + "en": "plane" + }, + "price": 17.99, + "speed": 100 +} +{ + "color": "blue", + "names": { + "de": "Flugzeug", + "en": "plane" + }, "price": 17.99, "speed": 100 } diff --git a/doc/mkdocs/docs/api/basic_json/update.md b/doc/mkdocs/docs/api/basic_json/update.md index a1837c860e..2807bf8978 100644 --- a/doc/mkdocs/docs/api/basic_json/update.md +++ b/doc/mkdocs/docs/api/basic_json/update.md @@ -2,13 +2,14 @@ ```cpp // (1) -void update(const_reference j); +void update(const_reference j, bool merge_objects = false); // (2) void update(const_iterator first, const_iterator last); ``` -1. Inserts all values from JSON object `j` and overwrites existing keys. +1. Inserts all values from JSON object `j`. and overwrites existing keys. When `merge_objects` is `#!c false` (default), + existing keys are overwritten. When `merge_objects` is `#!c true`, recursively merges objects with common keys. 2. Inserts all values from from range `[first, last)` and overwrites existing keys. The function is motivated by Python's [dict.update](https://docs.python.org/3.6/library/stdtypes.html#dict.update) @@ -19,6 +20,10 @@ function. `j` (in) : JSON object to read values from +`merge_objects` (in) +: when `#!c true`, existing keys are not overwritten, but contents of objects are merged recursively (default: + `#!c false`) + `first` (in) : begin of the range of elements to insert @@ -73,6 +78,63 @@ function. --8<-- "examples/update__range.output" ``` +??? example + + One common usecase for this function is the handling of user settings. Assume your application can configured in + some aspects: + + ```json + { + "color": "red", + "active": true, + "name": {"de": "Maus", "en": "mouse"} + } + ``` + + The user may override the default settings selectively: + + ```json + { + "color": "blue", + "name": {"es": "ratón"}, + } + ``` + + Then `update` manages the merging of default settings and user settings: + + ```cpp + auto user_settings = json::parse("config.json"); + auto effective_settings = get_default_settings(); + effective_settings.update(user_settings); + ``` + + Now `effective_settings` contains the default settings, but those keys set by the user are overwritten: + + ```json + { + "color": "blue", + "active": true, + "name": {"es": "ratón"} + } + ``` + + Note existing keys were just overwritten. To merge objects, `merge_objects` setting should be set to `#!c true`: + + ```cpp + auto user_settings = json::parse("config.json"); + auto effective_settings = get_default_settings(); + effective_settings.update(user_settings, true); + ``` + + ```json + { + "color": "blue", + "active": true, + "name": {"de": "Maus", "en": "mouse", "es": "ratón"} + } + ``` + ## Version history - Added in version 3.0.0. +- Added `merge_objects` parameter in 3.10.4. diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index b140577f2c..58a00a470c 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -5984,6 +5984,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec Inserts all values from JSON object @a j and overwrites existing keys. @param[in] j JSON object to read values from + @param[in] merge_objects when true, existing keys are not overwritten, but + contents of objects are merged recursively + (default: false) @throw type_error.312 if called on JSON values other than objects; example: `"cannot use update() with string"` @@ -5995,9 +5998,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update - @since version 3.0.0 + @since version 3.0.0, `merge_objects` parameter added in 3.10.4. */ - void update(const_reference j) + void update(const_reference j, bool merge_objects = false) { // implicitly convert null value to an empty object if (is_null()) @@ -6018,6 +6021,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec for (auto it = j.cbegin(); it != j.cend(); ++it) { + if (merge_objects && it.value().is_object()) + { + auto it2 = m_value.object->find(it.key()); + if (it2 != m_value.object->end()) + { + it2->second.update(it.value(), true); + continue; + } + } m_value.object->operator[](it.key()) = it.value(); #if JSON_DIAGNOSTICS m_value.object->operator[](it.key()).m_parent = this; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 25c6983b04..9e230b75fe 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -23484,7 +23484,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @since version 3.0.0 */ - void update(const_reference j) + void update(const_reference j, bool recursive = false) { // implicitly convert null value to an empty object if (is_null()) @@ -23505,6 +23505,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec for (auto it = j.cbegin(); it != j.cend(); ++it) { + if (recursive && it.value().is_object()) + { + auto it2 = m_value.object->find(it.key()); + if (it2 != m_value.object->end()) + { + it2->second.update(it.value(), recursive); + continue; + } + } m_value.object->operator[](it.key()) = it.value(); #if JSON_DIAGNOSTICS m_value.object->operator[](it.key()).m_parent = this; diff --git a/test/src/unit-diagnostics.cpp b/test/src/unit-diagnostics.cpp index a0bcceb279..ac04179fbb 100644 --- a/test/src/unit-diagnostics.cpp +++ b/test/src/unit-diagnostics.cpp @@ -96,7 +96,10 @@ TEST_CASE("Better diagnostics") json _; CHECK_THROWS_WITH_AS(_ = json::parse(""), "[json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", json::parse_error); } +} +TEST_CASE("Regression tests for extended diagnostics") +{ SECTION("Regression test for https://github.com/nlohmann/json/pull/2562#pullrequestreview-574858448") { CHECK_THROWS_WITH_AS(json({"0", "0"})[1].get(), "[json.exception.type_error.302] (/1) type must be number, but is string", json::type_error); @@ -110,7 +113,7 @@ TEST_CASE("Better diagnostics") CHECK_THROWS_WITH_AS(j.unflatten(), "[json.exception.type_error.315] (/~1foo) values in object must be primitive", json::type_error); } - SECTION("Regression test for https://github.com/nlohmann/json/issues/2838") + SECTION("Regression test for issue #2838 - Assertion failure when inserting into arrays with JSON_DIAGNOSTICS set") { // void push_back(basic_json&& val) { @@ -235,7 +238,7 @@ TEST_CASE("Better diagnostics") } } - SECTION("Regression test for https://github.com/nlohmann/json/issues/3032") + SECTION("Regression test for issue #3032 - Yet another assertion failure when inserting into arrays with JSON_DIAGNOSTICS set") { // reference operator[](size_type idx) { @@ -251,4 +254,4 @@ TEST_CASE("Better diagnostics") json j_arr_copy = j_arr; } } -} +} \ No newline at end of file diff --git a/test/src/unit-modifiers.cpp b/test/src/unit-modifiers.cpp index 1fb922a6b2..d6649ad806 100644 --- a/test/src/unit-modifiers.cpp +++ b/test/src/unit-modifiers.cpp @@ -796,64 +796,89 @@ 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("non-recursive (default)") { - SECTION("proper usage") + json j_object1 = {{"one", "eins"}, {"two", "zwei"}}; + json j_object2 = {{"three", "drei"}, {"two", "zwo"}}; + json j_array = {1, 2, 3, 4}; + + SECTION("const reference") { - j_object1.update(j_object2); - CHECK(j_object1 == json({{"one", "eins"}, {"two", "zwo"}, {"three", "drei"}})); + SECTION("proper usage") + { + 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); - } + 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.312] cannot use update() with array"); + 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.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.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.312] cannot use update() with array"); + } } - } - SECTION("iterator range") - { - SECTION("proper usage") + SECTION("iterator range") { - j_object1.update(j_object2.begin(), j_object2.end()); - CHECK(j_object1 == json({{"one", "eins"}, {"two", "zwo"}, {"three", "drei"}})); + SECTION("proper usage") + { + 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); - } + json j_null; + j_null.update(j_object2.begin(), j_object2.end()); + CHECK(j_null == j_object2); + } - SECTION("empty range") - { - j_object1.update(j_object2.begin(), j_object2.begin()); - CHECK(j_object1 == json({{"one", "eins"}, {"two", "zwei"}})); + 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.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()), + "[json.exception.invalid_iterator.202] iterators first and last must point to objects"); + } } + } - SECTION("invalid iterators") + SECTION("recursive") + { + SECTION("const reference") { - 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&); + SECTION("extend object") + { + json j1 = {{"string", "s"}, {"numbers", {{"one", 1}}}}; + json j2 = {{"string", "t"}, {"numbers", {{"two", 2}}}}; + j1.update(j2, true); + CHECK(j1 == json({{"string", "t"}, {"numbers", {{"one", 1}, {"two", 2}}}})); + } - CHECK_THROWS_WITH(j_array.update(j_object2.begin(), j_object2.end()), - "[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()), - "[json.exception.invalid_iterator.202] iterators first and last must point to objects"); + SECTION("replace object") + { + json j1 = {{"string", "s"}, {"numbers", {{"one", 1}}}}; + json j2 = {{"string", "t"}, {"numbers", 1}}; + j1.update(j2, true); + CHECK(j1 == json({{"string", "t"}, {"numbers", 1}})); + } } } } From 685327f48ec45d3294f706c2df9bd8d72fdf3ab2 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sat, 9 Oct 2021 15:51:14 +0200 Subject: [PATCH 2/4] :rotating_light: fix warning --- test/src/unit-diagnostics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/unit-diagnostics.cpp b/test/src/unit-diagnostics.cpp index ac04179fbb..98d02252e9 100644 --- a/test/src/unit-diagnostics.cpp +++ b/test/src/unit-diagnostics.cpp @@ -254,4 +254,4 @@ TEST_CASE("Regression tests for extended diagnostics") json j_arr_copy = j_arr; } } -} \ No newline at end of file +} From 9fba12704d15d6b59e6a66c20f6733447ac8acd3 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 10 Oct 2021 10:40:30 +0200 Subject: [PATCH 3/4] :sparkles: add recursive update function for iterator range --- doc/examples/update__range.cpp | 13 ++++-- doc/examples/update__range.output | 12 +++++ doc/mkdocs/docs/api/basic_json/update.md | 10 +++-- include/nlohmann/json.hpp | 50 +++++++-------------- single_include/nlohmann/json.hpp | 57 +++++++++--------------- 5 files changed, 62 insertions(+), 80 deletions(-) diff --git a/doc/examples/update__range.cpp b/doc/examples/update__range.cpp index 9f3e521a3a..98a390429d 100644 --- a/doc/examples/update__range.cpp +++ b/doc/examples/update__range.cpp @@ -7,12 +7,17 @@ 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; + json o1 = R"( {"color": "red", "price": 17.99, "names": {"de": "Flugzeug"}} )"_json; + json o2 = R"( {"color": "blue", "speed": 100, "names": {"en": "plane"}} )"_json; + json o3 = o1; - // add all keys from o2 to o1 (updating "color") + // add all keys from o2 to o1 (updating "color", replacing "names") o1.update(o2.begin(), o2.end()); - // output updated object o1 + // add all keys from o2 to o1 (updating "color", merging "names") + o3.update(o2.begin(), o2.end(), true); + + // output updated object o1 and o3 std::cout << std::setw(2) << o1 << '\n'; + std::cout << std::setw(2) << o3 << '\n'; } diff --git a/doc/examples/update__range.output b/doc/examples/update__range.output index 69f0965af7..c35a74513a 100644 --- a/doc/examples/update__range.output +++ b/doc/examples/update__range.output @@ -1,5 +1,17 @@ { "color": "blue", + "names": { + "en": "plane" + }, + "price": 17.99, + "speed": 100 +} +{ + "color": "blue", + "names": { + "de": "Flugzeug", + "en": "plane" + }, "price": 17.99, "speed": 100 } diff --git a/doc/mkdocs/docs/api/basic_json/update.md b/doc/mkdocs/docs/api/basic_json/update.md index 2807bf8978..851417e875 100644 --- a/doc/mkdocs/docs/api/basic_json/update.md +++ b/doc/mkdocs/docs/api/basic_json/update.md @@ -5,12 +5,14 @@ void update(const_reference j, bool merge_objects = false); // (2) -void update(const_iterator first, const_iterator last); +void update(const_iterator first, const_iterator last, bool merge_objects = false); ``` -1. Inserts all values from JSON object `j`. and overwrites existing keys. When `merge_objects` is `#!c false` (default), - existing keys are overwritten. When `merge_objects` is `#!c true`, recursively merges objects with common keys. -2. Inserts all values from from range `[first, last)` and overwrites existing keys. +1. Inserts all values from JSON object `j`. +2. Inserts all values from from range `[first, last)` + +When `merge_objects` is `#!c false` (default), existing keys are overwritten. When `merge_objects` is `#!c true`, +recursively merges objects with common keys. The function is motivated by Python's [dict.update](https://docs.python.org/3.6/library/stdtypes.html#dict.update) function. diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 58a00a470c..9852b18c85 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -6002,39 +6002,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec */ void update(const_reference j, bool merge_objects = false) { - // implicitly convert null value to an empty object - if (is_null()) - { - m_type = value_t::object; - m_value.object = create(); - assert_invariant(); - } - - if (JSON_HEDLEY_UNLIKELY(!is_object())) - { - JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()), *this)); - } - if (JSON_HEDLEY_UNLIKELY(!j.is_object())) - { - JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name()), *this)); - } - - for (auto it = j.cbegin(); it != j.cend(); ++it) - { - if (merge_objects && it.value().is_object()) - { - auto it2 = m_value.object->find(it.key()); - if (it2 != m_value.object->end()) - { - it2->second.update(it.value(), true); - continue; - } - } - m_value.object->operator[](it.key()) = it.value(); -#if JSON_DIAGNOSTICS - m_value.object->operator[](it.key()).m_parent = this; -#endif - } + update(j.begin(), j.end(), merge_objects); } /*! @@ -6045,6 +6013,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @param[in] first begin of the range of elements to insert @param[in] last end of the range of elements to insert + @param[in] merge_objects when true, existing keys are not overwritten, but + contents of objects are merged recursively + (default: false) @throw type_error.312 if called on JSON values other than objects; example: `"cannot use update() with string"` @@ -6061,9 +6032,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update - @since version 3.0.0 + @since version 3.0.0, `merge_objects` parameter added in 3.10.4. */ - void update(const_iterator first, const_iterator last) + void update(const_iterator first, const_iterator last, bool merge_objects = false) { // implicitly convert null value to an empty object if (is_null()) @@ -6093,6 +6064,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec for (auto it = first; it != last; ++it) { + if (merge_objects && it.value().is_object()) + { + auto it2 = m_value.object->find(it.key()); + if (it2 != m_value.object->end()) + { + it2->second.update(it.value(), true); + continue; + } + } m_value.object->operator[](it.key()) = it.value(); #if JSON_DIAGNOSTICS m_value.object->operator[](it.key()).m_parent = this; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 9e230b75fe..bf7212b4da 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -23471,6 +23471,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec Inserts all values from JSON object @a j and overwrites existing keys. @param[in] j JSON object to read values from + @param[in] merge_objects when true, existing keys are not overwritten, but + contents of objects are merged recursively + (default: false) @throw type_error.312 if called on JSON values other than objects; example: `"cannot use update() with string"` @@ -23482,43 +23485,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update - @since version 3.0.0 + @since version 3.0.0, `merge_objects` parameter added in 3.10.4. */ - void update(const_reference j, bool recursive = false) + void update(const_reference j, bool merge_objects = false) { - // implicitly convert null value to an empty object - if (is_null()) - { - m_type = value_t::object; - m_value.object = create(); - assert_invariant(); - } - - if (JSON_HEDLEY_UNLIKELY(!is_object())) - { - JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()), *this)); - } - if (JSON_HEDLEY_UNLIKELY(!j.is_object())) - { - JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name()), *this)); - } - - for (auto it = j.cbegin(); it != j.cend(); ++it) - { - if (recursive && it.value().is_object()) - { - auto it2 = m_value.object->find(it.key()); - if (it2 != m_value.object->end()) - { - it2->second.update(it.value(), recursive); - continue; - } - } - m_value.object->operator[](it.key()) = it.value(); -#if JSON_DIAGNOSTICS - m_value.object->operator[](it.key()).m_parent = this; -#endif - } + update(j.begin(), j.end(), merge_objects); } /*! @@ -23529,6 +23500,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @param[in] first begin of the range of elements to insert @param[in] last end of the range of elements to insert + @param[in] merge_objects when true, existing keys are not overwritten, but + contents of objects are merged recursively + (default: false) @throw type_error.312 if called on JSON values other than objects; example: `"cannot use update() with string"` @@ -23545,9 +23519,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update - @since version 3.0.0 + @since version 3.0.0, `merge_objects` parameter added in 3.10.4. */ - void update(const_iterator first, const_iterator last) + void update(const_iterator first, const_iterator last, bool merge_objects = false) { // implicitly convert null value to an empty object if (is_null()) @@ -23577,6 +23551,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec for (auto it = first; it != last; ++it) { + if (merge_objects && it.value().is_object()) + { + auto it2 = m_value.object->find(it.key()); + if (it2 != m_value.object->end()) + { + it2->second.update(it.value(), true); + continue; + } + } m_value.object->operator[](it.key()) = it.value(); #if JSON_DIAGNOSTICS m_value.object->operator[](it.key()).m_parent = this; From 3294bfe9fa530d79f0079d92daeb73a4c8427db1 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 10 Oct 2021 11:02:12 +0200 Subject: [PATCH 4/4] :hammer: adjust exceptions --- include/nlohmann/json.hpp | 10 ++++------ single_include/nlohmann/json.hpp | 10 ++++------ test/src/unit-diagnostics.cpp | 9 +++++++++ test/src/unit-modifiers.cpp | 4 ++-- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 9852b18c85..48748cd589 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -6019,9 +6019,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @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 type_error.312 if iterator @a first or @a last does does not + point to an object; example: `"cannot use update() with string"` @throw invalid_iterator.210 if @a first and @a last do not belong to the same JSON value; example: `"iterators do not fit"` @@ -6056,10 +6055,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // passed iterators must belong to objects - if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object() - || !last.m_object->is_object())) + if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) { - JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects", *this)); + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(first.m_object->type_name()), *first.m_object)); } for (auto it = first; it != last; ++it) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index bf7212b4da..9d1aa0d4fc 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -23506,9 +23506,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @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 type_error.312 if iterator @a first or @a last does does not + point to an object; example: `"cannot use update() with string"` @throw invalid_iterator.210 if @a first and @a last do not belong to the same JSON value; example: `"iterators do not fit"` @@ -23543,10 +23542,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // passed iterators must belong to objects - if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object() - || !last.m_object->is_object())) + if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) { - JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects", *this)); + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(first.m_object->type_name()), *first.m_object)); } for (auto it = first; it != last; ++it) diff --git a/test/src/unit-diagnostics.cpp b/test/src/unit-diagnostics.cpp index 98d02252e9..87d4eb370f 100644 --- a/test/src/unit-diagnostics.cpp +++ b/test/src/unit-diagnostics.cpp @@ -96,6 +96,15 @@ TEST_CASE("Better diagnostics") json _; CHECK_THROWS_WITH_AS(_ = json::parse(""), "[json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", json::parse_error); } + + SECTION("Wrong type in update()") + { + json j = {{"foo", "bar"}}; + json k = {{"bla", 1}}; + + CHECK_THROWS_WITH_AS(j.update(k["bla"].begin(), k["bla"].end()), "[json.exception.type_error.312] (/bla) cannot use update() with number", json::type_error); + CHECK_THROWS_WITH_AS(j.update(k["bla"]), "[json.exception.type_error.312] (/bla) cannot use update() with number", json::type_error); + } } TEST_CASE("Regression tests for extended diagnostics") diff --git a/test/src/unit-modifiers.cpp b/test/src/unit-modifiers.cpp index d6649ad806..e8fe427c63 100644 --- a/test/src/unit-modifiers.cpp +++ b/test/src/unit-modifiers.cpp @@ -848,14 +848,14 @@ TEST_CASE("modifiers") 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_AS(j_object1.update(j_array.begin(), j_array.end()), json::type_error&); CHECK_THROWS_WITH(j_array.update(j_object2.begin(), j_object2.end()), "[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()), - "[json.exception.invalid_iterator.202] iterators first and last must point to objects"); + "[json.exception.type_error.312] cannot use update() with array"); } } }