From 85de93ba93a001a7d709c8ec9e0a9ae7fbd9598a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Tue, 6 Jun 2017 13:44:35 +0200 Subject: [PATCH 1/2] add std::pair support --- src/json.hpp | 36 +++++++++++++++++++++++++++++++--- test/src/unit-constructor1.cpp | 26 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 4efe51f7..87e4e546 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -285,7 +285,7 @@ class invalid_iterator : public exception Exceptions have ids 3xx. -name / id | example massage | description +name / id | example message | description ----------------------------- | --------------- | ------------------------- json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead. json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. @@ -324,7 +324,7 @@ class type_error : public exception Exceptions have ids 4xx. -name / id | example massage | description +name / id | example message | description ------------------------------- | --------------- | ------------------------- json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1. json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it. @@ -355,9 +355,10 @@ class out_of_range : public exception Exceptions have ids 5xx. -name / id | example massage | description +name / id | example message | description ------------------------------ | --------------- | ------------------------- json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. +json.exception.other_error.502 | invalid object size for conversion | Some conversions to user-defined types impose constraints on the object size (e.g. std::pair) @since version 3.0.0 */ @@ -865,6 +866,14 @@ void to_json(BasicJsonType& j, T (&arr)[N]) external_constructor::construct(j, arr); } +template ::value, int> = 0> +void to_json(BasicJsonType& j, std::pair const& p) +{ + j[p.first] = p.second; +} + /////////////// // from_json // /////////////// @@ -1086,6 +1095,27 @@ void from_json(const BasicJsonType& j, ArithmeticType& val) } } +template ::value, int> = 0> +void from_json(const BasicJsonType& j, std::pair& p) +{ + if (not j.is_object()) + { + JSON_THROW(type_error::create(302, "type must be object, but is " + j.type_name())); + } + + auto const inner_object = j.template get_ptr(); + auto const size = inner_object->size(); + if (size != 1) + { + JSON_THROW(other_error::create(502, "conversion to std::pair requires the object to have exactly one field, but it has " + std::to_string(size))); + } + auto const& obj = *inner_object->begin(); + // cannot use *inner_object, need to convert both members + p = std::make_pair(obj.first, obj.second.template get()); +} + struct to_json_fn { private: diff --git a/test/src/unit-constructor1.cpp b/test/src/unit-constructor1.cpp index 18c032e0..f16e15c1 100644 --- a/test/src/unit-constructor1.cpp +++ b/test/src/unit-constructor1.cpp @@ -156,6 +156,20 @@ TEST_CASE("constructors") CHECK(j == j_reference); } + SECTION("std::pair") + { + std::pair p{"first", "second"}; + json j(p); + + CHECK((j.get() == p)); + + std::pair p2{"first", 1}; + // use char const* + json j2(std::make_pair("first", 1)); + + CHECK((j2.get() == p2)); + } + SECTION("std::map") { std::map o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}}; @@ -164,6 +178,7 @@ TEST_CASE("constructors") CHECK(j == j_reference); } + SECTION("std::multimap") { std::multimap o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}}; @@ -954,6 +969,17 @@ TEST_CASE("constructors") "[json.exception.type_error.301] cannot create object from initializer list"); } + SECTION("std::pair with error") + { + json j{{"too", "much"}, {"string", "fields"}}; + CHECK_THROWS_AS((j.get>()), json::other_error); + CHECK_THROWS_WITH((j.get>()), + "[json.exception.other_error.502] conversion " + "to std::pair requires the object to have " + "exactly one field, but it has 2"); + } + + SECTION("empty array") { json j = json::array(); From cea39dfaa892115adb05985221cfa2090f92bab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Tue, 6 Jun 2017 14:18:32 +0200 Subject: [PATCH 2/2] fix #600 Instead of calling CompatibleObjectType iterator-range constructor, first convert json::value_type to CompatibleObjectType::value_type --- src/json.hpp | 28 +++++++++++++++----- test/src/unit-constructor1.cpp | 48 +++++++++++++++++++++++++--------- 2 files changed, 56 insertions(+), 20 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 87e4e546..d5618fb8 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -867,11 +867,11 @@ void to_json(BasicJsonType& j, T (&arr)[N]) } template ::value, int> = 0> + enable_if_t::value, int> = 0> void to_json(BasicJsonType& j, std::pair const& p) { - j[p.first] = p.second; + j[p.first] = p.second; } /////////////// @@ -1046,10 +1046,24 @@ void from_json(const BasicJsonType& j, CompatibleObjectType& obj) auto inner_object = j.template get_ptr(); using std::begin; using std::end; + using value_type = typename CompatibleObjectType::value_type; + std::vector v; + v.reserve(j.size()); + std::transform( + inner_object->begin(), inner_object->end(), std::back_inserter(v), + [](typename BasicJsonType::object_t::value_type const & p) + { + return value_type + { + p.first, + p.second + .template get()}; + }); // we could avoid the assignment, but this might require a for loop, which // might be less efficient than the container constructor for some // containers (would it?) - obj = CompatibleObjectType(begin(*inner_object), end(*inner_object)); + obj = CompatibleObjectType(std::make_move_iterator(begin(v)), + std::make_move_iterator(end(v))); } // overload for arithmetic types, not chosen for basic_json template arguments @@ -1096,8 +1110,8 @@ void from_json(const BasicJsonType& j, ArithmeticType& val) } template ::value, int> = 0> + enable_if_t::value, int> = 0> void from_json(const BasicJsonType& j, std::pair& p) { if (not j.is_object()) @@ -1112,7 +1126,7 @@ void from_json(const BasicJsonType& j, std::pair& p) JSON_THROW(other_error::create(502, "conversion to std::pair requires the object to have exactly one field, but it has " + std::to_string(size))); } auto const& obj = *inner_object->begin(); - // cannot use *inner_object, need to convert both members + // cannot use *inner_object, need to convert both members p = std::make_pair(obj.first, obj.second.template get()); } diff --git a/test/src/unit-constructor1.cpp b/test/src/unit-constructor1.cpp index f16e15c1..db100a69 100644 --- a/test/src/unit-constructor1.cpp +++ b/test/src/unit-constructor1.cpp @@ -158,16 +158,27 @@ TEST_CASE("constructors") SECTION("std::pair") { - std::pair p{"first", "second"}; - json j(p); + std::pair p{"first", "second"}; + json j(p); - CHECK((j.get() == p)); + CHECK((j.get() == p)); - std::pair p2{"first", 1}; - // use char const* - json j2(std::make_pair("first", 1)); + std::pair p2{"first", 1}; + // use char const* + json j2(std::make_pair("first", 1)); - CHECK((j2.get() == p2)); + CHECK((j2.get() == p2)); + } + + SECTION("std::map #600") + { + std::map m; + m["a"] = "b"; + m["c"] = "d"; + m["e"] = "f"; + + json j(m); + CHECK((j.get() == m)); } SECTION("std::map") @@ -971,12 +982,23 @@ TEST_CASE("constructors") SECTION("std::pair with error") { - json j{{"too", "much"}, {"string", "fields"}}; - CHECK_THROWS_AS((j.get>()), json::other_error); - CHECK_THROWS_WITH((j.get>()), - "[json.exception.other_error.502] conversion " - "to std::pair requires the object to have " - "exactly one field, but it has 2"); + SECTION("wrong field number") + { + json j{{"too", "much"}, {"string", "fields"}}; + CHECK_THROWS_AS((j.get>()), json::other_error); + CHECK_THROWS_WITH((j.get>()), + "[json.exception.other_error.502] conversion " + "to std::pair requires the object to have " + "exactly one field, but it has 2"); + } + + SECTION("wrong JSON type") + { + json j(42); + CHECK_THROWS_AS((j.get>()), json::type_error); + CHECK_THROWS_WITH((j.get>()), + "[json.exception.type_error.302] type must be object, but is number"); + } }