From 1be73b903eb3eae244cf5c5336b29e96cf4f1098 Mon Sep 17 00:00:00 2001 From: Niels Date: Fri, 11 Nov 2016 19:29:14 +0100 Subject: [PATCH 1/2] proposal for emplace/emplace_back (#349) --- doc/examples/emplace.cpp | 23 +++++++++ doc/examples/emplace.link | 1 + doc/examples/emplace.output | 4 ++ doc/examples/emplace_back.cpp | 23 +++++++++ doc/examples/emplace_back.link | 1 + doc/examples/emplace_back.output | 4 ++ doc/index.md | 11 ++++- src/json.hpp | 84 ++++++++++++++++++++++++++++++++ src/json.hpp.re2c | 84 ++++++++++++++++++++++++++++++++ test/src/unit-modifiers.cpp | 68 ++++++++++++++++++++++++++ 10 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 doc/examples/emplace.cpp create mode 100644 doc/examples/emplace.link create mode 100644 doc/examples/emplace.output create mode 100644 doc/examples/emplace_back.cpp create mode 100644 doc/examples/emplace_back.link create mode 100644 doc/examples/emplace_back.output diff --git a/doc/examples/emplace.cpp b/doc/examples/emplace.cpp new file mode 100644 index 00000000..6014a361 --- /dev/null +++ b/doc/examples/emplace.cpp @@ -0,0 +1,23 @@ +#include + +using json = nlohmann::json; + +int main() +{ + // create JSON values + json object = {{"one", 1}, {"two", 2}}; + json null; + + // print values + std::cout << object << '\n'; + std::cout << null << '\n'; + + // add values + object.emplace("three", 3); + null.emplace("A", "a"); + null.emplace("B", "b"); + + // print values + std::cout << object << '\n'; + std::cout << null << '\n'; +} diff --git a/doc/examples/emplace.link b/doc/examples/emplace.link new file mode 100644 index 00000000..e78fd0e5 --- /dev/null +++ b/doc/examples/emplace.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/emplace.output b/doc/examples/emplace.output new file mode 100644 index 00000000..2bc9c002 --- /dev/null +++ b/doc/examples/emplace.output @@ -0,0 +1,4 @@ +{"one":1,"two":2} +null +{"one":1,"three":3,"two":2} +{"A":"a","B":"b"} diff --git a/doc/examples/emplace_back.cpp b/doc/examples/emplace_back.cpp new file mode 100644 index 00000000..4e9ec89a --- /dev/null +++ b/doc/examples/emplace_back.cpp @@ -0,0 +1,23 @@ +#include + +using json = nlohmann::json; + +int main() +{ + // create JSON values + json array = {1, 2, 3, 4, 5}; + json null; + + // print values + std::cout << array << '\n'; + std::cout << null << '\n'; + + // add values + array.emplace_back(6); + null.emplace_back("first"); + null.emplace_back(3, "second"); + + // print values + std::cout << array << '\n'; + std::cout << null << '\n'; +} diff --git a/doc/examples/emplace_back.link b/doc/examples/emplace_back.link new file mode 100644 index 00000000..4363e4c7 --- /dev/null +++ b/doc/examples/emplace_back.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/emplace_back.output b/doc/examples/emplace_back.output new file mode 100644 index 00000000..bdd80d82 --- /dev/null +++ b/doc/examples/emplace_back.output @@ -0,0 +1,4 @@ +[1,2,3,4,5] +null +[1,2,3,4,5,6] +["first",["second","second","second"]] diff --git a/doc/index.md b/doc/index.md index fcb5b432..70161d0e 100644 --- a/doc/index.md +++ b/doc/index.md @@ -197,7 +197,7 @@ The container functions known from STL have been extended to support the differe @link nlohmann::basic_json::max_size `max_size` @endlink (returns `0`) - modifiers + modifiers `clear` @link nlohmann::basic_json::clear `clear` @endlink @link nlohmann::basic_json::clear `clear` @endlink @@ -233,6 +233,15 @@ The container functions known from STL have been extended to support the differe throws `std::domain_error` @link nlohmann::basic_json::push_back(const typename object_t::value_type & val) `push_back` @endlink (creates object)
@link nlohmann::basic_json::push_back(const nlohmann::basic_json &) `push_back` @endlink (creates array) + + `emplace` / `emplace_back` + @link nlohmann::basic_json::emplace() `emplace` @endlink + @link nlohmann::basic_json::emplace_back() `emplace_back` @endlink + throws `std::domain_error` + throws `std::domain_error` + throws `std::domain_error` + @link nlohmann::basic_json::emplace() `emplace` @endlink (creates object)
@link nlohmann::basic_json::emplace_back() `emplace_back` @endlink (creates array) + `swap` @link nlohmann::basic_json::swap `swap` @endlink diff --git a/src/json.hpp b/src/json.hpp index a302bb02..3c8d3f82 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -5053,6 +5053,90 @@ class basic_json return *this; } + /*! + @brief add an object to an array + + Creates a JSON value from the passed parameters @a args to the end of the + JSON value. If the function is called on a JSON null value, an empty array + is created before appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @throw std::domain_error when called on a type other than JSON array or + null; example: `"cannot use emplace_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` can be used to add + elements to a JSON array. Note how the `null` value was silently converted + to a JSON array.,emplace_back} + + @since version 2.0.8 + */ + template + void emplace_back(Args&& ... args) + { + // emplace_back only works for null objects or arrays + if (not(is_null() or is_array())) + { + throw std::domain_error("cannot use emplace_back() with " + type_name()); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (perfect forwarding) + m_value.array->emplace_back(std::forward(args)...); + } + + /*! + @brief add an object to an object + + Creates a JSON value from the passed parameters @a args to the JSON + object. If the function is called on a JSON null value, an empty object + is created before appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @throw std::domain_error when called on a type other than JSON object or + null; example: `"cannot use emplace() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `emplace()` can be used to add elements + to a JSON object. Note how the `null` value was silently converted to a + JSON object.,emplace} + + @since version 2.0.8 + */ + template + void emplace(Args&& ... args) + { + // emplace only works for null objects or arrays + if (not(is_null() or is_object())) + { + throw std::domain_error("cannot use emplace() with " + type_name()); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array (perfect forwarding) + m_value.object->emplace(std::forward(args)...); + } + /*! @brief inserts element diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index b829889d..3173d799 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -5053,6 +5053,90 @@ class basic_json return *this; } + /*! + @brief add an object to an array + + Creates a JSON value from the passed parameters @a args to the end of the + JSON value. If the function is called on a JSON null value, an empty array + is created before appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @throw std::domain_error when called on a type other than JSON array or + null; example: `"cannot use emplace_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` can be used to add + elements to a JSON array. Note how the `null` value was silently converted + to a JSON array.,emplace_back} + + @since version 2.0.8 + */ + template + void emplace_back(Args&& ... args) + { + // emplace_back only works for null objects or arrays + if (not(is_null() or is_array())) + { + throw std::domain_error("cannot use emplace_back() with " + type_name()); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (perfect forwarding) + m_value.array->emplace_back(std::forward(args)...); + } + + /*! + @brief add an object to an object + + Creates a JSON value from the passed parameters @a args to the JSON + object. If the function is called on a JSON null value, an empty object + is created before appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @throw std::domain_error when called on a type other than JSON object or + null; example: `"cannot use emplace() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `emplace()` can be used to add elements + to a JSON object. Note how the `null` value was silently converted to a + JSON object.,emplace} + + @since version 2.0.8 + */ + template + void emplace(Args&& ... args) + { + // emplace only works for null objects or arrays + if (not(is_null() or is_object())) + { + throw std::domain_error("cannot use emplace() with " + type_name()); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array (perfect forwarding) + m_value.object->emplace(std::forward(args)...); + } + /*! @brief inserts element diff --git a/test/src/unit-modifiers.cpp b/test/src/unit-modifiers.cpp index fd7fed56..499edde7 100644 --- a/test/src/unit-modifiers.cpp +++ b/test/src/unit-modifiers.cpp @@ -258,6 +258,74 @@ TEST_CASE("modifiers") } } + SECTION("emplace_back()") + { + SECTION("to array") + { + SECTION("null") + { + json j; + j.emplace_back(1); + j.emplace_back(2); + CHECK(j.type() == json::value_t::array); + CHECK(j == json({1, 2})); + } + + SECTION("array") + { + json j = {1, 2, 3}; + j.emplace_back("Hello"); + CHECK(j.type() == json::value_t::array); + CHECK(j == json({1, 2, 3, "Hello"})); + } + + SECTION("multiple values") + { + json j; + j.emplace_back(3, "foo"); + CHECK(j.type() == json::value_t::array); + CHECK(j == json({{"foo", "foo", "foo"}})); + } + } + + SECTION("other type") + { + json j = 1; + CHECK_THROWS_AS(j.emplace_back("Hello"), std::domain_error); + CHECK_THROWS_WITH(j.emplace_back("Hello"), "cannot use emplace_back() with number"); + } + } + + SECTION("emplace()") + { + SECTION("to object") + { + SECTION("null") + { + json j; + j.emplace("foo", "bar"); + j.emplace("baz", "bam"); + CHECK(j.type() == json::value_t::object); + CHECK(j == json({{"baz", "bam"}, {"foo", "bar"}})); + } + + SECTION("object") + { + json j = {{"foo", "bar"}}; + j.emplace("baz", "bam"); + CHECK(j.type() == json::value_t::object); + CHECK(j == json({{"baz", "bam"}, {"foo", "bar"}})); + } + } + + SECTION("other type") + { + json j = 1; + CHECK_THROWS_AS(j.emplace("foo", "bar"), std::domain_error); + CHECK_THROWS_WITH(j.emplace("foo", "bar"), "cannot use emplace() with number"); + } + } + SECTION("operator+=") { SECTION("to array") From aeb4f87a278776b9166c6eb8484264a84b19c7af Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 28 Nov 2016 18:33:46 +0100 Subject: [PATCH 2/2] :sparkles: added return value for emplace (#349) --- doc/examples/emplace.cpp | 9 ++++++++- doc/examples/emplace.link | 2 +- doc/examples/emplace.output | 2 ++ src/json.hpp | 26 +++++++++++++++++++------- src/json.hpp.re2c | 26 +++++++++++++++++++------- test/src/unit-modifiers.cpp | 37 +++++++++++++++++++++++++++++++++---- 6 files changed, 82 insertions(+), 20 deletions(-) diff --git a/doc/examples/emplace.cpp b/doc/examples/emplace.cpp index 6014a361..c3b3c3e3 100644 --- a/doc/examples/emplace.cpp +++ b/doc/examples/emplace.cpp @@ -13,11 +13,18 @@ int main() std::cout << null << '\n'; // add values - object.emplace("three", 3); + auto res1 = object.emplace("three", 3); null.emplace("A", "a"); null.emplace("B", "b"); + // the following call will not add an object, because there is already + // a value stored at key "B" + auto res2 = null.emplace("B", "c"); + // print values std::cout << object << '\n'; + std::cout << *res1.first << " " << std::boolalpha << res1.second << '\n'; + std::cout << null << '\n'; + std::cout << *res2.first << " " << std::boolalpha << res2.second << '\n'; } diff --git a/doc/examples/emplace.link b/doc/examples/emplace.link index e78fd0e5..a9366c32 100644 --- a/doc/examples/emplace.link +++ b/doc/examples/emplace.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/emplace.output b/doc/examples/emplace.output index 2bc9c002..83d6f773 100644 --- a/doc/examples/emplace.output +++ b/doc/examples/emplace.output @@ -1,4 +1,6 @@ {"one":1,"two":2} null {"one":1,"three":3,"two":2} +3 true {"A":"a","B":"b"} +"b" false diff --git a/src/json.hpp b/src/json.hpp index fdc57dff..36afd56b 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -5075,15 +5075,20 @@ class basic_json } /*! - @brief add an object to an object + @brief add an object to an object if key does not exist - Creates a JSON value from the passed parameters @a args to the JSON - object. If the function is called on a JSON null value, an empty object - is created before appending the value created from @a args. + Inserts a new element into a JSON object constructed in-place with the given + @a args if there is no element with the key in the container. If the + function is called on a JSON null value, an empty object is created before + appending the value created from @a args. @param[in] args arguments to forward to a constructor of @ref basic_json @tparam Args compatible types to create a @ref basic_json object + @return a pair consisting of an iterator to the inserted element, or the + already-existing element if no insertion happened, and a bool + denoting whether the insertion took place. + @throw std::domain_error when called on a type other than JSON object or null; example: `"cannot use emplace() with number"` @@ -5091,12 +5096,13 @@ class basic_json @liveexample{The example shows how `emplace()` can be used to add elements to a JSON object. Note how the `null` value was silently converted to a - JSON object.,emplace} + JSON object. Further note how no value is added if there was already one + value stored with the same key.,emplace} @since version 2.0.8 */ template - void emplace(Args&& ... args) + std::pair emplace(Args&& ... args) { // emplace only works for null objects or arrays if (not(is_null() or is_object())) @@ -5113,7 +5119,13 @@ class basic_json } // add element to array (perfect forwarding) - m_value.object->emplace(std::forward(args)...); + auto res = m_value.object->emplace(std::forward(args)...); + // create result iterator and set iterator to the result of emplace + auto it = begin(); + it.m_it.object_iterator = res.first; + + // return pair of iterator and boolean + return {it, res.second}; } /*! diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index 383f4b8f..b629dc61 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -5075,15 +5075,20 @@ class basic_json } /*! - @brief add an object to an object + @brief add an object to an object if key does not exist - Creates a JSON value from the passed parameters @a args to the JSON - object. If the function is called on a JSON null value, an empty object - is created before appending the value created from @a args. + Inserts a new element into a JSON object constructed in-place with the given + @a args if there is no element with the key in the container. If the + function is called on a JSON null value, an empty object is created before + appending the value created from @a args. @param[in] args arguments to forward to a constructor of @ref basic_json @tparam Args compatible types to create a @ref basic_json object + @return a pair consisting of an iterator to the inserted element, or the + already-existing element if no insertion happened, and a bool + denoting whether the insertion took place. + @throw std::domain_error when called on a type other than JSON object or null; example: `"cannot use emplace() with number"` @@ -5091,12 +5096,13 @@ class basic_json @liveexample{The example shows how `emplace()` can be used to add elements to a JSON object. Note how the `null` value was silently converted to a - JSON object.,emplace} + JSON object. Further note how no value is added if there was already one + value stored with the same key.,emplace} @since version 2.0.8 */ template - void emplace(Args&& ... args) + std::pair emplace(Args&& ... args) { // emplace only works for null objects or arrays if (not(is_null() or is_object())) @@ -5113,7 +5119,13 @@ class basic_json } // add element to array (perfect forwarding) - m_value.object->emplace(std::forward(args)...); + auto res = m_value.object->emplace(std::forward(args)...); + // create result iterator and set iterator to the result of emplace + auto it = begin(); + it.m_it.object_iterator = res.first; + + // return pair of iterator and boolean + return {it, res.second}; } /*! diff --git a/test/src/unit-modifiers.cpp b/test/src/unit-modifiers.cpp index 499edde7..e715d55f 100644 --- a/test/src/unit-modifiers.cpp +++ b/test/src/unit-modifiers.cpp @@ -302,18 +302,47 @@ TEST_CASE("modifiers") { SECTION("null") { + // start with a null value json j; - j.emplace("foo", "bar"); - j.emplace("baz", "bam"); + + // add a new key + auto res1 = j.emplace("foo", "bar"); + CHECK(res1.second == true); + CHECK(*res1.first == "bar"); + + // the null value is changed to an object CHECK(j.type() == json::value_t::object); + + // add a new key + auto res2 = j.emplace("baz", "bam"); + CHECK(res2.second == true); + CHECK(*res2.first == "bam"); + + // we try to insert at given key - no change + auto res3 = j.emplace("baz", "bad"); + CHECK(res3.second == false); + CHECK(*res3.first == "bam"); + + // the final object CHECK(j == json({{"baz", "bam"}, {"foo", "bar"}})); } SECTION("object") { + // start with an object json j = {{"foo", "bar"}}; - j.emplace("baz", "bam"); - CHECK(j.type() == json::value_t::object); + + // add a new key + auto res1 = j.emplace("baz", "bam"); + CHECK(res1.second == true); + CHECK(*res1.first == "bam"); + + // add an existing key + auto res2 = j.emplace("foo", "bad"); + CHECK(res2.second == false); + CHECK(*res2.first == "bar"); + + // check final object CHECK(j == json({{"baz", "bam"}, {"foo", "bar"}})); } }