diff --git a/src/json.hpp b/src/json.hpp index b83ae8b4..b8bd4f60 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -7,26 +7,6 @@ @see https://github.com/nlohmann/json */ -/*! -@defgroup container Container -@brief C++ Container concept - -A Container is an object used to store other objects and taking care of the -management of the memory used by the objects it contains. - -@see http://en.cppreference.com/w/cpp/concept/Container - -@defgroup reversiblecontainer Reversible Container -@brief C++ Reversible Container concept -@ingroup container - -A ReversibleContainer is a Container that has iterators that meet the -requirements of either BidirectionalIterator or RandomAccessIterator. Such -iterators allow a ReversibleContainer to be iterated over in reverse. - -@see http://en.cppreference.com/w/cpp/concept/ReversibleContainer -*/ - #ifndef _NLOHMANN_JSON #define _NLOHMANN_JSON @@ -871,15 +851,12 @@ class basic_json case (value_t::array): { T to_vector; - //to_vector.reserve(m_value.array->size()); std::transform(m_value.array->begin(), m_value.array->end(), std::inserter(to_vector, to_vector.end()), [](basic_json i) { return i.get(); }); return to_vector; - - // return T(m_value.array->begin(), m_value.array->end()); } default: { @@ -1187,6 +1164,129 @@ class basic_json return m_value.object->operator[](key); } + /// remove element given an iterator + template ::value or + std::is_same::value + , int>::type + = 0> + inline T erase(T pos) + { + // make sure iterator fits the current value + if (this != pos.m_object or m_type != pos.m_object->m_type) + { + throw std::runtime_error("iterator does not fit current value"); + } + + T result = end(); + + switch (m_type) + { + case value_t::number_integer: + case value_t::number_float: + case value_t::boolean: + case value_t::string: + { + if (pos.m_it.generic_iterator != 0) + { + throw std::out_of_range("iterator out of range"); + } + + if (m_type == value_t::string) + { + delete m_value.string; + m_value.string = nullptr; + } + + m_type = value_t::null; + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); + break; + } + + default: + { + throw std::runtime_error("cannot use erase with " + type_name()); + } + } + + return result; + } + + /// remove elements given an iterator range + template ::value or + std::is_same::value + , int>::type + = 0> + inline T erase(T first, T last) + { + // make sure iterator fits the current value + if (this != first.m_object or this != last.m_object or + m_type != first.m_object->m_type or m_type != last.m_object->m_type) + { + throw std::runtime_error("iterators do not fit current value"); + } + + T result = end(); + + switch (m_type) + { + case value_t::number_integer: + case value_t::number_float: + case value_t::boolean: + case value_t::string: + { + if (first.m_it.generic_iterator != 0 or last.m_it.generic_iterator != 1) + { + throw std::out_of_range("iterators out of range"); + } + + if (m_type == value_t::string) + { + delete m_value.string; + m_value.string = nullptr; + } + + m_type = value_t::null; + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + { + throw std::runtime_error("cannot use erase with " + type_name()); + } + } + + return result; + } + /// remove element from an object given a key inline size_type erase(const typename object_t::key_type& key) { diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index 6a23b51e..a7e7bf9c 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -7,26 +7,6 @@ @see https://github.com/nlohmann/json */ -/*! -@defgroup container Container -@brief C++ Container concept - -A Container is an object used to store other objects and taking care of the -management of the memory used by the objects it contains. - -@see http://en.cppreference.com/w/cpp/concept/Container - -@defgroup reversiblecontainer Reversible Container -@brief C++ Reversible Container concept -@ingroup container - -A ReversibleContainer is a Container that has iterators that meet the -requirements of either BidirectionalIterator or RandomAccessIterator. Such -iterators allow a ReversibleContainer to be iterated over in reverse. - -@see http://en.cppreference.com/w/cpp/concept/ReversibleContainer -*/ - #ifndef _NLOHMANN_JSON #define _NLOHMANN_JSON @@ -871,15 +851,12 @@ class basic_json case (value_t::array): { T to_vector; - //to_vector.reserve(m_value.array->size()); std::transform(m_value.array->begin(), m_value.array->end(), std::inserter(to_vector, to_vector.end()), [](basic_json i) { return i.get(); }); return to_vector; - - // return T(m_value.array->begin(), m_value.array->end()); } default: { @@ -1187,6 +1164,129 @@ class basic_json return m_value.object->operator[](key); } + /// remove element given an iterator + template ::value or + std::is_same::value + , int>::type + = 0> + inline T erase(T pos) + { + // make sure iterator fits the current value + if (this != pos.m_object or m_type != pos.m_object->m_type) + { + throw std::runtime_error("iterator does not fit current value"); + } + + T result = end(); + + switch (m_type) + { + case value_t::number_integer: + case value_t::number_float: + case value_t::boolean: + case value_t::string: + { + if (pos.m_it.generic_iterator != 0) + { + throw std::out_of_range("iterator out of range"); + } + + if (m_type == value_t::string) + { + delete m_value.string; + m_value.string = nullptr; + } + + m_type = value_t::null; + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); + break; + } + + default: + { + throw std::runtime_error("cannot use erase with " + type_name()); + } + } + + return result; + } + + /// remove elements given an iterator range + template ::value or + std::is_same::value + , int>::type + = 0> + inline T erase(T first, T last) + { + // make sure iterator fits the current value + if (this != first.m_object or this != last.m_object or + m_type != first.m_object->m_type or m_type != last.m_object->m_type) + { + throw std::runtime_error("iterators do not fit current value"); + } + + T result = end(); + + switch (m_type) + { + case value_t::number_integer: + case value_t::number_float: + case value_t::boolean: + case value_t::string: + { + if (first.m_it.generic_iterator != 0 or last.m_it.generic_iterator != 1) + { + throw std::out_of_range("iterators out of range"); + } + + if (m_type == value_t::string) + { + delete m_value.string; + m_value.string = nullptr; + } + + m_type = value_t::null; + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + { + throw std::runtime_error("cannot use erase with " + type_name()); + } + } + + return result; + } + /// remove element from an object given a key inline size_type erase(const typename object_t::key_type& key) { diff --git a/test/unit.cpp b/test/unit.cpp index a5abe59e..5b20218a 100644 --- a/test/unit.cpp +++ b/test/unit.cpp @@ -2294,7 +2294,7 @@ TEST_CASE("element access") SECTION("remove specified element") { - SECTION("remove element") + SECTION("remove element by index") { { json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; @@ -2337,7 +2337,112 @@ TEST_CASE("element access") } } - SECTION("access on non-object type") + SECTION("remove element by iterator") + { + SECTION("erase(begin())") + { + { + json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json::iterator it2 = jarray.erase(jarray.begin()); + CHECK(jarray == json({true, nullptr, "string", 42.23, json::object(), {1, 2, 3}})); + CHECK(*it2 == json(true)); + } + { + json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json::const_iterator it2 = jarray.erase(jarray.cbegin()); + CHECK(jarray == json({true, nullptr, "string", 42.23, json::object(), {1, 2, 3}})); + CHECK(*it2 == json(true)); + } + } + + SECTION("erase(begin(), end())") + { + { + json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json::iterator it2 = jarray.erase(jarray.begin(), jarray.end()); + CHECK(jarray == json::array()); + CHECK(it2 == jarray.end()); + } + { + json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json::const_iterator it2 = jarray.erase(jarray.cbegin(), jarray.cend()); + CHECK(jarray == json::array()); + CHECK(it2 == jarray.cend()); + } + } + + SECTION("erase(begin(), begin())") + { + { + json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json::iterator it2 = jarray.erase(jarray.begin(), jarray.begin()); + CHECK(jarray == json({1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}})); + CHECK(*it2 == json(1)); + } + { + json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json::const_iterator it2 = jarray.erase(jarray.cbegin(), jarray.cbegin()); + CHECK(jarray == json({1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}})); + CHECK(*it2 == json(1)); + } + } + + SECTION("erase at offset") + { + { + json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json::iterator it = jarray.begin() + 3; + json::iterator it2 = jarray.erase(it); + CHECK(jarray == json({1, true, nullptr, 42.23, json::object(), {1, 2, 3}})); + CHECK(*it2 == json(42.23)); + } + { + json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json::const_iterator it = jarray.cbegin() + 3; + json::const_iterator it2 = jarray.erase(it); + CHECK(jarray == json({1, true, nullptr, 42.23, json::object(), {1, 2, 3}})); + CHECK(*it2 == json(42.23)); + } + } + + SECTION("erase subrange") + { + { + json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json::iterator it2 = jarray.erase(jarray.begin() + 2, jarray.begin() + 5); + CHECK(jarray == json({1, true, json::object(), {1, 2, 3}})); + CHECK(*it2 == json::object()); + } + { + json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json::const_iterator it2 = jarray.erase(jarray.cbegin() + 2, jarray.cbegin() + 5); + CHECK(jarray == json({1, true, json::object(), {1, 2, 3}})); + CHECK(*it2 == json::object()); + } + } + + SECTION("different arrays") + { + { + json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json jarray2 = {"foo", "bar"}; + CHECK_THROWS_AS(jarray.erase(jarray2.begin()), std::runtime_error); + CHECK_THROWS_AS(jarray.erase(jarray.begin(), jarray2.end()), std::runtime_error); + CHECK_THROWS_AS(jarray.erase(jarray2.begin(), jarray.end()), std::runtime_error); + CHECK_THROWS_AS(jarray.erase(jarray2.begin(), jarray2.end()), std::runtime_error); + } + { + json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; + json jarray2 = {"foo", "bar"}; + CHECK_THROWS_AS(jarray.erase(jarray2.cbegin()), std::runtime_error); + CHECK_THROWS_AS(jarray.erase(jarray.cbegin(), jarray2.cend()), std::runtime_error); + CHECK_THROWS_AS(jarray.erase(jarray2.cbegin(), jarray.cend()), std::runtime_error); + CHECK_THROWS_AS(jarray.erase(jarray2.cbegin(), jarray2.cend()), std::runtime_error); + } + } + } + + SECTION("remove element by index in non-array type") { SECTION("null") { @@ -2569,7 +2674,7 @@ TEST_CASE("element access") SECTION("remove specified element") { - SECTION("remove element") + SECTION("remove element by key") { CHECK(j.find("integer") != j.end()); CHECK(j.erase("integer") == 1); @@ -2607,7 +2712,112 @@ TEST_CASE("element access") CHECK(j.erase("array") == 0); } - SECTION("access on non-object type") + SECTION("remove element by iterator") + { + SECTION("erase(begin())") + { + { + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json::iterator it2 = jobject.erase(jobject.begin()); + CHECK(jobject == json({{"b", 1}, {"c", 17}})); + CHECK(*it2 == json(1)); + } + { + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json::const_iterator it2 = jobject.erase(jobject.cbegin()); + CHECK(jobject == json({{"b", 1}, {"c", 17}})); + CHECK(*it2 == json(1)); + } + } + + SECTION("erase(begin(), end())") + { + { + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json::iterator it2 = jobject.erase(jobject.begin(), jobject.end()); + CHECK(jobject == json::object()); + CHECK(it2 == jobject.end()); + } + { + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json::const_iterator it2 = jobject.erase(jobject.cbegin(), jobject.cend()); + CHECK(jobject == json::object()); + CHECK(it2 == jobject.cend()); + } + } + + SECTION("erase(begin(), begin())") + { + { + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json::iterator it2 = jobject.erase(jobject.begin(), jobject.begin()); + CHECK(jobject == json({{"a", "a"}, {"b", 1}, {"c", 17}})); + CHECK(*it2 == json("a")); + } + { + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json::const_iterator it2 = jobject.erase(jobject.cbegin(), jobject.cbegin()); + CHECK(jobject == json({{"a", "a"}, {"b", 1}, {"c", 17}})); + CHECK(*it2 == json("a")); + } + } + + SECTION("erase at offset") + { + { + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json::iterator it = jobject.find("b"); + json::iterator it2 = jobject.erase(it); + CHECK(jobject == json({{"a", "a"}, {"c", 17}})); + CHECK(*it2 == json(17)); + } + { + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}}; + json::const_iterator it = jobject.find("b"); + json::const_iterator it2 = jobject.erase(it); + CHECK(jobject == json({{"a", "a"}, {"c", 17}})); + CHECK(*it2 == json(17)); + } + } + + SECTION("erase subrange") + { + { + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}, {"d", false}, {"e", true}}; + json::iterator it2 = jobject.erase(jobject.find("b"), jobject.find("e")); + CHECK(jobject == json({{"a", "a"}, {"e", true}})); + CHECK(*it2 == json(true)); + } + { + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}, {"d", false}, {"e", true}}; + json::const_iterator it2 = jobject.erase(jobject.find("b"), jobject.find("e")); + CHECK(jobject == json({{"a", "a"}, {"e", true}})); + CHECK(*it2 == json(true)); + } + } + + SECTION("different arrays") + { + { + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}, {"d", false}, {"e", true}}; + json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17}}; + CHECK_THROWS_AS(jobject.erase(jobject2.begin()), std::runtime_error); + CHECK_THROWS_AS(jobject.erase(jobject.begin(), jobject2.end()), std::runtime_error); + CHECK_THROWS_AS(jobject.erase(jobject2.begin(), jobject.end()), std::runtime_error); + CHECK_THROWS_AS(jobject.erase(jobject2.begin(), jobject2.end()), std::runtime_error); + } + { + json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}, {"d", false}, {"e", true}}; + json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17}}; + CHECK_THROWS_AS(jobject.erase(jobject2.cbegin()), std::runtime_error); + CHECK_THROWS_AS(jobject.erase(jobject.cbegin(), jobject2.cend()), std::runtime_error); + CHECK_THROWS_AS(jobject.erase(jobject2.cbegin(), jobject.cend()), std::runtime_error); + CHECK_THROWS_AS(jobject.erase(jobject2.cbegin(), jobject2.cend()), std::runtime_error); + } + } + } + + SECTION("remove element by key in non-object type") { SECTION("null") { @@ -2807,6 +3017,253 @@ TEST_CASE("element access") } } } + + SECTION("other values") + { + SECTION("erase with one valid iterator") + { + SECTION("string") + { + { + json j = "foo"; + json::iterator it = j.erase(j.begin()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + { + json j = "bar"; + json::const_iterator it = j.erase(j.cbegin()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + } + + SECTION("number (boolean)") + { + { + json j = false; + json::iterator it = j.erase(j.begin()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + { + json j = true; + json::const_iterator it = j.erase(j.cbegin()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + } + + SECTION("number (integer)") + { + { + json j = 17; + json::iterator it = j.erase(j.begin()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + { + json j = 17; + json::const_iterator it = j.erase(j.cbegin()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + } + + SECTION("number (floating point)") + { + { + json j = 23.42; + json::iterator it = j.erase(j.begin()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + { + json j = 23.42; + json::const_iterator it = j.erase(j.cbegin()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + } + } + + SECTION("erase with one invalid iterator") + { + SECTION("string") + { + { + json j = "foo"; + CHECK_THROWS_AS(j.erase(j.end()), std::out_of_range); + } + { + json j = "bar"; + CHECK_THROWS_AS(j.erase(j.cend()), std::out_of_range); + } + } + + SECTION("number (boolean)") + { + { + json j = false; + CHECK_THROWS_AS(j.erase(j.end()), std::out_of_range); + } + { + json j = true; + CHECK_THROWS_AS(j.erase(j.cend()), std::out_of_range); + } + } + + SECTION("number (integer)") + { + { + json j = 17; + CHECK_THROWS_AS(j.erase(j.end()), std::out_of_range); + } + { + json j = 17; + CHECK_THROWS_AS(j.erase(j.cend()), std::out_of_range); + } + } + + SECTION("number (floating point)") + { + { + json j = 23.42; + CHECK_THROWS_AS(j.erase(j.end()), std::out_of_range); + } + { + json j = 23.42; + CHECK_THROWS_AS(j.erase(j.cend()), std::out_of_range); + } + } + } + + SECTION("erase with two valid iterators") + { + SECTION("string") + { + { + json j = "foo"; + json::iterator it = j.erase(j.begin(), j.end()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + { + json j = "bar"; + json::const_iterator it = j.erase(j.cbegin(), j.cend()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + } + + SECTION("number (boolean)") + { + { + json j = false; + json::iterator it = j.erase(j.begin(), j.end()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + { + json j = true; + json::const_iterator it = j.erase(j.cbegin(), j.cend()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + } + + SECTION("number (integer)") + { + { + json j = 17; + json::iterator it = j.erase(j.begin(), j.end()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + { + json j = 17; + json::const_iterator it = j.erase(j.cbegin(), j.cend()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + } + + SECTION("number (floating point)") + { + { + json j = 23.42; + json::iterator it = j.erase(j.begin(), j.end()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + { + json j = 23.42; + json::const_iterator it = j.erase(j.cbegin(), j.cend()); + CHECK(j.type() == json::value_t::null); + CHECK(it == j.end()); + } + } + } + + SECTION("erase with two invalid iterators") + { + SECTION("string") + { + { + json j = "foo"; + CHECK_THROWS_AS(j.erase(j.end(), j.end()), std::out_of_range); + CHECK_THROWS_AS(j.erase(j.begin(), j.begin()), std::out_of_range); + } + { + json j = "bar"; + CHECK_THROWS_AS(j.erase(j.cend(), j.cend()), std::out_of_range); + CHECK_THROWS_AS(j.erase(j.cbegin(), j.cbegin()), std::out_of_range); + } + } + + SECTION("number (boolean)") + { + { + json j = false; + CHECK_THROWS_AS(j.erase(j.end(), j.end()), std::out_of_range); + CHECK_THROWS_AS(j.erase(j.begin(), j.begin()), std::out_of_range); + } + { + json j = true; + CHECK_THROWS_AS(j.erase(j.cend(), j.cend()), std::out_of_range); + CHECK_THROWS_AS(j.erase(j.cbegin(), j.cbegin()), std::out_of_range); + } + } + + SECTION("number (integer)") + { + { + json j = 17; + CHECK_THROWS_AS(j.erase(j.end(), j.end()), std::out_of_range); + CHECK_THROWS_AS(j.erase(j.begin(), j.begin()), std::out_of_range); + } + { + json j = 17; + CHECK_THROWS_AS(j.erase(j.cend(), j.cend()), std::out_of_range); + CHECK_THROWS_AS(j.erase(j.cbegin(), j.cbegin()), std::out_of_range); + } + } + + SECTION("number (floating point)") + { + { + json j = 23.42; + CHECK_THROWS_AS(j.erase(j.end(), j.end()), std::out_of_range); + CHECK_THROWS_AS(j.erase(j.begin(), j.begin()), std::out_of_range); + } + { + json j = 23.42; + CHECK_THROWS_AS(j.erase(j.cend(), j.cend()), std::out_of_range); + CHECK_THROWS_AS(j.erase(j.cbegin(), j.cbegin()), std::out_of_range); + } + } + } + } } TEST_CASE("iterators")