Skip to content

Commit

Permalink
working on #235
Browse files Browse the repository at this point in the history
  • Loading branch information
nlohmann committed May 8, 2016
1 parent fadf286 commit 9ecf83f
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 2 deletions.
26 changes: 26 additions & 0 deletions doc/examples/push_back__initializer_list.cpp
@@ -0,0 +1,26 @@
#include <json.hpp>

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});
}
1 change: 1 addition & 0 deletions doc/examples/push_back__initializer_list.link
@@ -0,0 +1 @@
<a target="_blank" href="http://melpon.org/wandbox/permlink/wZF4dRHjfCyjb3rx"><b>online</b></a>
4 changes: 4 additions & 0 deletions 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]]
50 changes: 49 additions & 1 deletion src/json.hpp
Expand Up @@ -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<basic_json>`, 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<basic_json> 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<basic_json>)
*/
reference operator+=(std::initializer_list<basic_json> init)
{
push_back(init);
return *this;
}

/*!
Expand Down
50 changes: 49 additions & 1 deletion src/json.hpp.re2c
Expand Up @@ -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<basic_json>`, 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<basic_json> 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<basic_json>)
*/
reference operator+=(std::initializer_list<basic_json> init)
{
push_back(init);
return *this;
}

/*!
Expand Down
81 changes: 81 additions & 0 deletions test/unit.cpp
Expand Up @@ -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+=")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 9ecf83f

Please sign in to comment.