From 9ecf83f630fc950f0e0b67bd99ecf68f2b8afa68 Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 8 May 2016 20:28:20 +0200 Subject: [PATCH] working on #235 --- doc/examples/push_back__initializer_list.cpp | 26 ++++++ doc/examples/push_back__initializer_list.link | 1 + .../push_back__initializer_list.output | 4 + src/json.hpp | 50 +++++++++++- src/json.hpp.re2c | 50 +++++++++++- test/unit.cpp | 81 +++++++++++++++++++ 6 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 doc/examples/push_back__initializer_list.cpp create mode 100644 doc/examples/push_back__initializer_list.link create mode 100644 doc/examples/push_back__initializer_list.output diff --git a/doc/examples/push_back__initializer_list.cpp b/doc/examples/push_back__initializer_list.cpp new file mode 100644 index 0000000000..9fe01ad798 --- /dev/null +++ b/doc/examples/push_back__initializer_list.cpp @@ -0,0 +1,26 @@ +#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.push_back({"three", 3}); // object is extended + object += {"four", 4}; // object is extended + null.push_back({"five", 5}); // null is converted to array + + // print values + std::cout << object << '\n'; + std::cout << null << '\n'; + + // would throw: + //object.push_back({1, 2, 3}); +} diff --git a/doc/examples/push_back__initializer_list.link b/doc/examples/push_back__initializer_list.link new file mode 100644 index 0000000000..2b5abaeda8 --- /dev/null +++ b/doc/examples/push_back__initializer_list.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/push_back__initializer_list.output b/doc/examples/push_back__initializer_list.output new file mode 100644 index 0000000000..668eb25d74 --- /dev/null +++ b/doc/examples/push_back__initializer_list.output @@ -0,0 +1,4 @@ +{"one":1,"two":2} +null +{"four":4,"one":1,"three":3,"two":2} +[["five",5]] diff --git a/src/json.hpp b/src/json.hpp index 5153655018..37080ebb77 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -4878,7 +4878,55 @@ class basic_json reference operator+=(const typename object_t::value_type& val) { push_back(val); - return operator[](val.first); + return *this; + } + + /*! + @brief add an object to an object + + This function allows to use `push_back` with an initializer list. In case + + 1. the current value is an object, + 2. the initializer list @a init contains only two elements, and + 3. the first element of @a init is a string, + + @a init is converted into an object element and added using + @ref push_back(const typename object_t::value_type&). Otherwise, @a init + is converted to a JSON value and added using @ref push_back(basic_json&&). + + @param init an initializer list + + @complexity Linear in the size of the initializer list @a init. + + @note This function is required to resolve an ambiguous overload error, + because pairs like `{"key", "value"}` can be both interpreted as + `object_t::value_type` or `std::initializer_list`, see + https://github.com/nlohmann/json/issues/235 for more information. + + @liveexample{The example shows how initializer lists are treated as + objects when possible.,push_back__initializer_list} + */ + void push_back(std::initializer_list init) + { + if (is_object() and init.size() == 2 and init.begin()->is_string()) + { + const string_t key = *init.begin(); + push_back(typename object_t::value_type(key, *(init.begin() + 1))); + } + else + { + push_back(basic_json(init)); + } + } + + /*! + @brief add an object to an object + @copydoc push_back(std::initializer_list) + */ + reference operator+=(std::initializer_list init) + { + push_back(init); + return *this; } /*! diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index c8b66e920f..4321f0aa34 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -4878,7 +4878,55 @@ class basic_json reference operator+=(const typename object_t::value_type& val) { push_back(val); - return operator[](val.first); + return *this; + } + + /*! + @brief add an object to an object + + This function allows to use `push_back` with an initializer list. In case + + 1. the current value is an object, + 2. the initializer list @a init contains only two elements, and + 3. the first element of @a init is a string, + + @a init is converted into an object element and added using + @ref push_back(const typename object_t::value_type&). Otherwise, @a init + is converted to a JSON value and added using @ref push_back(basic_json&&). + + @param init an initializer list + + @complexity Linear in the size of the initializer list @a init. + + @note This function is required to resolve an ambiguous overload error, + because pairs like `{"key", "value"}` can be both interpreted as + `object_t::value_type` or `std::initializer_list`, see + https://github.com/nlohmann/json/issues/235 for more information. + + @liveexample{The example shows how initializer lists are treated as + objects when possible.,push_back__initializer_list} + */ + void push_back(std::initializer_list init) + { + if (is_object() and init.size() == 2 and init.begin()->is_string()) + { + const string_t key = *init.begin(); + push_back(typename object_t::value_type(key, *(init.begin() + 1))); + } + else + { + push_back(basic_json(init)); + } + } + + /*! + @brief add an object to an object + @copydoc push_back(std::initializer_list) + */ + reference operator+=(std::initializer_list init) + { + push_back(init); + return *this; } /*! diff --git a/test/unit.cpp b/test/unit.cpp index af52e17515..e42430c35e 100644 --- a/test/unit.cpp +++ b/test/unit.cpp @@ -7920,6 +7920,42 @@ TEST_CASE("modifiers") "cannot use push_back() with number"); } } + + SECTION("with initializer_list") + { + SECTION("null") + { + json j; + j.push_back({"foo", "bar"}); + CHECK(j == json::array({{"foo", "bar"}})); + + json k; + k.push_back({1, 2, 3}); + CHECK(k == json::array({{1, 2, 3}})); + } + + SECTION("array") + { + json j = {1, 2, 3}; + j.push_back({"foo", "bar"}); + CHECK(j == json({1, 2, 3, {"foo", "bar"}})); + + json k = {1, 2, 3}; + k.push_back({1, 2, 3}); + CHECK(k == json({1, 2, 3, {1, 2, 3}})); + } + + SECTION("object") + { + json j = {{"key1", 1}}; + j.push_back({"key2", "bar"}); + CHECK(j == json({{"key1", 1}, {"key2", "bar"}})); + + json k = {{"key1", 1}}; + CHECK_THROWS_AS(k.push_back({1, 2, 3, 4}), std::domain_error); + CHECK_THROWS_WITH(k.push_back({1, 2, 3, 4}), "cannot use push_back() with object"); + } + } } SECTION("operator+=") @@ -8016,6 +8052,42 @@ TEST_CASE("modifiers") "cannot use push_back() with number"); } } + + SECTION("with initializer_list") + { + SECTION("null") + { + json j; + j += {"foo", "bar"}; + CHECK(j == json::array({{"foo", "bar"}})); + + json k; + k += {1, 2, 3}; + CHECK(k == json::array({{1, 2, 3}})); + } + + SECTION("array") + { + json j = {1, 2, 3}; + j += {"foo", "bar"}; + CHECK(j == json({1, 2, 3, {"foo", "bar"}})); + + json k = {1, 2, 3}; + k += {1, 2, 3}; + CHECK(k == json({1, 2, 3, {1, 2, 3}})); + } + + SECTION("object") + { + json j = {{"key1", 1}}; + j += {"key2", "bar"}; + CHECK(j == json({{"key1", 1}, {"key2", "bar"}})); + + json k = {{"key1", 1}}; + CHECK_THROWS_AS((k += {1, 2, 3, 4}), std::domain_error); + CHECK_THROWS_WITH((k += {1, 2, 3, 4}), "cannot use push_back() with object"); + } + } } SECTION("insert") @@ -13992,6 +14064,15 @@ TEST_CASE("regression tests") CHECK(dest == expected); } + + SECTION("issue ##235 - ambiguous overload for 'push_back' and 'operator+='") + { + json data = {{"key", "value"}}; + data.push_back({"key2", "value2"}); + data += {"key3", "value3"}; + + CHECK(data == json({{"key", "value"}, {"key2", "value2"}, {"key3", "value3"}})); + } } // special test case to check if memory is leaked if constructor throws