diff --git a/include/nlohmann/ordered_map.hpp b/include/nlohmann/ordered_map.hpp index 2a72fefd0..576790915 100644 --- a/include/nlohmann/ordered_map.hpp +++ b/include/nlohmann/ordered_map.hpp @@ -18,6 +18,7 @@ template , using mapped_type = T; using Container = std::vector, Allocator>; using typename Container::iterator; + using typename Container::const_iterator; using typename Container::size_type; using typename Container::value_type; @@ -30,7 +31,7 @@ template , ordered_map(std::initializer_list init, const Allocator& alloc = Allocator() ) : Container{init, alloc} {} - std::pair emplace(key_type&& key, T&& t) + std::pair emplace(const key_type& key, T&& t) { for (auto it = this->begin(); it != this->end(); ++it) { @@ -43,9 +44,40 @@ template , return {--this->end(), true}; } - T& operator[](Key&& key) + T& operator[](const Key& key) { - return emplace(std::move(key), T{}).first->second; + return emplace(key, T{}).first->second; + } + + const T& operator[](const Key& key) const + { + return at(key); + } + + T& at(const Key& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it->second; + } + } + + throw std::out_of_range("key not found"); + } + + const T& at(const Key& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it->second; + } + } + + throw std::out_of_range("key not found"); } size_type erase(const Key& key) @@ -66,6 +98,74 @@ template , } return 0; } + + iterator erase(iterator pos) + { + auto it = pos; + + // Since we cannot move const Keys, re-construct them in place + for (auto next = it; ++next != this->end(); ++it) + { + it->~value_type(); // Destroy but keep allocation + new (&*it) value_type{std::move(*next)}; + } + Container::pop_back(); + return pos; + } + + size_type count(const Key& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return 1; + } + } + return 0; + } + + iterator find(const Key& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it; + } + } + return Container::end(); + } + + const_iterator find(const Key& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it; + } + } + return Container::end(); + } + + std::pair insert( value_type&& value ) + { + return emplace(value.first, std::move(value.second)); + } + + std::pair insert( const value_type& value ) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == value.first) + { + return {it, false}; + } + } + Container::push_back(value); + return {--this->end(), true}; + } }; } // namespace nlohmann diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 6a0f47ab4..9c66a8457 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -16408,6 +16408,7 @@ template , using mapped_type = T; using Container = std::vector, Allocator>; using typename Container::iterator; + using typename Container::const_iterator; using typename Container::size_type; using typename Container::value_type; @@ -16420,7 +16421,7 @@ template , ordered_map(std::initializer_list init, const Allocator& alloc = Allocator() ) : Container{init, alloc} {} - std::pair emplace(key_type&& key, T&& t) + std::pair emplace(const key_type& key, T&& t) { for (auto it = this->begin(); it != this->end(); ++it) { @@ -16433,9 +16434,40 @@ template , return {--this->end(), true}; } - T& operator[](Key&& key) + T& operator[](const Key& key) { - return emplace(std::move(key), T{}).first->second; + return emplace(key, T{}).first->second; + } + + const T& operator[](const Key& key) const + { + return at(key); + } + + T& at(const Key& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it->second; + } + } + + throw std::out_of_range("key not found"); + } + + const T& at(const Key& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it->second; + } + } + + throw std::out_of_range("key not found"); } size_type erase(const Key& key) @@ -16456,6 +16488,74 @@ template , } return 0; } + + iterator erase(iterator pos) + { + auto it = pos; + + // Since we cannot move const Keys, re-construct them in place + for (auto next = it; ++next != this->end(); ++it) + { + it->~value_type(); // Destroy but keep allocation + new (&*it) value_type{std::move(*next)}; + } + Container::pop_back(); + return pos; + } + + size_type count(const Key& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return 1; + } + } + return 0; + } + + iterator find(const Key& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it; + } + } + return Container::end(); + } + + const_iterator find(const Key& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it; + } + } + return Container::end(); + } + + std::pair insert( value_type&& value ) + { + return emplace(value.first, std::move(value.second)); + } + + std::pair insert( const value_type& value ) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == value.first) + { + return {it, false}; + } + } + Container::push_back(value); + return {--this->end(), true}; + } }; } // namespace nlohmann diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 76ba31d88..3d8bceb70 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -126,10 +126,12 @@ set(files src/unit-msgpack.cpp src/unit-noexcept.cpp src/unit-ordered_json.cpp + src/unit-ordered_map.cpp src/unit-pointer_access.cpp src/unit-readme.cpp src/unit-reference_access.cpp - src/unit-regression.cpp + src/unit-regression1.cpp + src/unit-regression2.cpp src/unit-serialization.cpp src/unit-testsuites.cpp src/unit-to_chars.cpp diff --git a/test/src/unit-ordered_map.cpp b/test/src/unit-ordered_map.cpp new file mode 100644 index 000000000..49937aa22 --- /dev/null +++ b/test/src/unit-ordered_map.cpp @@ -0,0 +1,292 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ (test suite) +| | |__ | | | | | | version 3.9.0 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2019 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "doctest_compatibility.h" + +#include +using nlohmann::ordered_map; + + +TEST_CASE("ordered_map") +{ + SECTION("constructor") + { + SECTION("constructor from iterator range") + { + std::map m {{"eins", "one"}, {"zwei", "two"}, {"drei", "three"}}; + ordered_map om(m.begin(), m.end()); + CHECK(om.size() == 3); + } + + SECTION("copy assignment") + { + std::map m {{"eins", "one"}, {"zwei", "two"}, {"drei", "three"}}; + ordered_map om(m.begin(), m.end()); + const auto com = om; + CHECK(com.size() == 3); + } + } + + SECTION("at") + { + std::map m {{"eins", "one"}, {"zwei", "two"}, {"drei", "three"}}; + ordered_map om(m.begin(), m.end()); + const auto com = om; + + SECTION("with Key&&") + { + CHECK(om.at(std::string("eins")) == std::string("one")); + CHECK(com.at(std::string("eins")) == std::string("one")); + CHECK_THROWS_AS(om.at(std::string("vier")), std::out_of_range); + CHECK_THROWS_AS(com.at(std::string("vier")), std::out_of_range); + } + + SECTION("with const Key&&") + { + const std::string eins = "eins"; + const std::string vier = "vier"; + CHECK(om.at(eins) == std::string("one")); + CHECK(com.at(eins) == std::string("one")); + CHECK_THROWS_AS(om.at(vier), std::out_of_range); + CHECK_THROWS_AS(com.at(vier), std::out_of_range); + } + + SECTION("with string literal") + { + CHECK(om.at("eins") == std::string("one")); + CHECK(com.at("eins") == std::string("one")); + CHECK_THROWS_AS(om.at("vier"), std::out_of_range); + CHECK_THROWS_AS(com.at("vier"), std::out_of_range); + } + } + + SECTION("operator[]") + { + std::map m {{"eins", "one"}, {"zwei", "two"}, {"drei", "three"}}; + ordered_map om(m.begin(), m.end()); + const auto com = om; + + SECTION("with Key&&") + { + CHECK(om[std::string("eins")] == std::string("one")); + CHECK(com[std::string("eins")] == std::string("one")); + + CHECK(om[std::string("vier")] == std::string("")); + CHECK(om.size() == 4); + } + + SECTION("with const Key&&") + { + const std::string eins = "eins"; + const std::string vier = "vier"; + + CHECK(om[eins] == std::string("one")); + CHECK(com[eins] == std::string("one")); + + CHECK(om[vier] == std::string("")); + CHECK(om.size() == 4); + } + + SECTION("with string literal") + { + CHECK(om["eins"] == std::string("one")); + CHECK(com["eins"] == std::string("one")); + + CHECK(om["vier"] == std::string("")); + CHECK(om.size() == 4); + } + } + + SECTION("erase") + { + ordered_map om; + om["eins"] = "one"; + om["zwei"] = "two"; + om["drei"] = "three"; + + { + auto it = om.begin(); + CHECK(it->first == "eins"); + ++it; + CHECK(it->first == "zwei"); + ++it; + CHECK(it->first == "drei"); + ++it; + CHECK(it == om.end()); + } + + SECTION("with Key&&") + { + CHECK(om.size() == 3); + CHECK(om.erase(std::string("eins")) == 1); + CHECK(om.size() == 2); + CHECK(om.erase(std::string("vier")) == 0); + CHECK(om.size() == 2); + + auto it = om.begin(); + CHECK(it->first == "zwei"); + ++it; + CHECK(it->first == "drei"); + ++it; + CHECK(it == om.end()); + } + + SECTION("with const Key&&") + { + const std::string eins = "eins"; + const std::string vier = "vier"; + CHECK(om.size() == 3); + CHECK(om.erase(eins) == 1); + CHECK(om.size() == 2); + CHECK(om.erase(vier) == 0); + CHECK(om.size() == 2); + + auto it = om.begin(); + CHECK(it->first == "zwei"); + ++it; + CHECK(it->first == "drei"); + ++it; + CHECK(it == om.end()); + } + + SECTION("with string literal") + { + CHECK(om.size() == 3); + CHECK(om.erase("eins") == 1); + CHECK(om.size() == 2); + CHECK(om.erase("vier") == 0); + CHECK(om.size() == 2); + + auto it = om.begin(); + CHECK(it->first == "zwei"); + ++it; + CHECK(it->first == "drei"); + ++it; + CHECK(it == om.end()); + } + + SECTION("with iterator") + { + CHECK(om.size() == 3); + CHECK(om.begin()->first == "eins"); + CHECK(std::next(om.begin(), 1)->first == "zwei"); + CHECK(std::next(om.begin(), 2)->first == "drei"); + + auto it = om.erase(om.begin()); + CHECK(it->first == "zwei"); + CHECK(om.size() == 2); + + auto it2 = om.begin(); + CHECK(it2->first == "zwei"); + ++it2; + CHECK(it2->first == "drei"); + ++it2; + CHECK(it2 == om.end()); + } + } + + SECTION("count") + { + ordered_map om; + om["eins"] = "one"; + om["zwei"] = "two"; + om["drei"] = "three"; + + const std::string eins("eins"); + const std::string vier("vier"); + CHECK(om.count("eins") == 1); + CHECK(om.count(std::string("eins")) == 1); + CHECK(om.count(eins) == 1); + CHECK(om.count("vier") == 0); + CHECK(om.count(std::string("vier")) == 0); + CHECK(om.count(vier) == 0); + } + + SECTION("find") + { + ordered_map om; + om["eins"] = "one"; + om["zwei"] = "two"; + om["drei"] = "three"; + const auto com = om; + + const std::string eins("eins"); + const std::string vier("vier"); + CHECK(om.find("eins") == om.begin()); + CHECK(om.find(std::string("eins")) == om.begin()); + CHECK(om.find(eins) == om.begin()); + CHECK(om.find("vier") == om.end()); + CHECK(om.find(std::string("vier")) == om.end()); + CHECK(om.find(vier) == om.end()); + + CHECK(com.find("eins") == com.begin()); + CHECK(com.find(std::string("eins")) == com.begin()); + CHECK(com.find(eins) == com.begin()); + CHECK(com.find("vier") == com.end()); + CHECK(com.find(std::string("vier")) == com.end()); + CHECK(com.find(vier) == com.end()); + } + + SECTION("insert") + { + ordered_map om; + om["eins"] = "one"; + om["zwei"] = "two"; + om["drei"] = "three"; + + SECTION("const value_type&") + { + ordered_map::value_type vt1 {"eins", "1"}; + ordered_map::value_type vt4 {"vier", "four"}; + + auto res1 = om.insert(vt1); + CHECK(res1.first == om.begin()); + CHECK(res1.second == false); + CHECK(om.size() == 3); + + auto res4 = om.insert(vt4); + CHECK(res4.first == om.begin() + 3); + CHECK(res4.second == true); + CHECK(om.size() == 4); + } + + SECTION("value_type&&") + { + auto res1 = om.insert({"eins", "1"}); + CHECK(res1.first == om.begin()); + CHECK(res1.second == false); + CHECK(om.size() == 3); + + auto res4 = om.insert({"vier", "four"}); + CHECK(res4.first == om.begin() + 3); + CHECK(res4.second == true); + CHECK(om.size() == 4); + } + } +} diff --git a/test/src/unit-regression.cpp b/test/src/unit-regression1.cpp similarity index 81% rename from test/src/unit-regression.cpp rename to test/src/unit-regression1.cpp index 223466a4f..97f64e716 100644 --- a/test/src/unit-regression.cpp +++ b/test/src/unit-regression1.cpp @@ -128,83 +128,9 @@ struct nocopy j = {{"val", n.val}}; } }; - -struct Data -{ - Data() = default; - Data(const std::string& a_, const std::string b_) : a(a_), b(b_) {} - std::string a {}; - std::string b {}; -}; - -void from_json(const json& j, Data& data) -{ - j["a"].get_to(data.a); - j["b"].get_to(data.b); } -bool operator==(Data const& lhs, Data const& rhs) -{ - return lhs.a == rhs.a && lhs.b == rhs.b; -} - -//bool operator!=(Data const& lhs, Data const& rhs) -//{ -// return !(lhs == rhs); -//} -} - -///////////////////////////////////////////////////////////////////// -// for #1021 -///////////////////////////////////////////////////////////////////// - -using float_json = nlohmann::basic_json; - -///////////////////////////////////////////////////////////////////// -// for #1647 -///////////////////////////////////////////////////////////////////// -namespace -{ -struct NonDefaultFromJsonStruct { }; - -inline bool operator== (NonDefaultFromJsonStruct const&, NonDefaultFromJsonStruct const&) -{ - return true; -} - -enum class for_1647 { one, two }; - -NLOHMANN_JSON_SERIALIZE_ENUM(for_1647, -{ - {for_1647::one, "one"}, - {for_1647::two, "two"}, -}) -} - -namespace nlohmann -{ -template <> -struct adl_serializer -{ - static NonDefaultFromJsonStruct from_json (json const&) noexcept - { - return {}; - } -}; -} - -///////////////////////////////////////////////////////////////////// -// for #1805 -///////////////////////////////////////////////////////////////////// - -struct NotSerializableData -{ - int mydata; - float myfloat; -}; - - -TEST_CASE("regression tests") +TEST_CASE("regression tests 1") { SECTION("issue #60 - Double quotation mark is not parsed correctly") { @@ -1651,331 +1577,6 @@ TEST_CASE("regression tests") CHECK(ff.x == 3); nlohmann::json nj = lj; // This line works as expected } - - SECTION("issue #1001 - Fix memory leak during parser callback") - { - auto geojsonExample = R"( - { "type": "FeatureCollection", - "features": [ - { "type": "Feature", - "geometry": {"type": "Point", "coordinates": [102.0, 0.5]}, - "properties": {"prop0": "value0"} - }, - { "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0] - ] - }, - "properties": { - "prop0": "value0", - "prop1": 0.0 - } - }, - { "type": "Feature", - "geometry": { - "type": "Polygon", - "coordinates": [ - [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], - [100.0, 1.0], [100.0, 0.0] ] - ] - }, - "properties": { - "prop0": "value0", - "prop1": {"this": "that"} - } - } - ] - })"; - - json::parser_callback_t cb = [&](int, json::parse_event_t event, json & parsed) - { - // skip uninteresting events - if (event == json::parse_event_t::value && !parsed.is_primitive()) - { - return false; - } - - switch (event) - { - case json::parse_event_t::key: - { - return true; - } - case json::parse_event_t::value: - { - return false; - } - case json::parse_event_t::object_start: - { - return true; - } - case json::parse_event_t::object_end: - { - return false; - } - case json::parse_event_t::array_start: - { - return true; - } - case json::parse_event_t::array_end: - { - return false; - } - - default: - { - return true; - } - } - }; - - auto j = json::parse(geojsonExample, cb, true); - CHECK(j == json()); - } - - SECTION("issue #1021 - to/from_msgpack only works with standard typization") - { - float_json j = 1000.0; - CHECK(float_json::from_cbor(float_json::to_cbor(j)) == j); - CHECK(float_json::from_msgpack(float_json::to_msgpack(j)) == j); - CHECK(float_json::from_ubjson(float_json::to_ubjson(j)) == j); - - float_json j2 = {1000.0, 2000.0, 3000.0}; - CHECK(float_json::from_ubjson(float_json::to_ubjson(j2, true, true)) == j2); - } - - SECTION("issue #1045 - Using STL algorithms with JSON containers with expected results?") - { - json diffs = nlohmann::json::array(); - json m1{{"key1", 42}}; - json m2{{"key2", 42}}; - auto p1 = m1.items(); - auto p2 = m2.items(); - - using it_type = decltype(p1.begin()); - - std::set_difference( - p1.begin(), p1.end(), - p2.begin(), p2.end(), - std::inserter(diffs, diffs.end()), [&](const it_type & e1, const it_type & e2) -> bool - { - using comper_pair = std::pair; // Trying to avoid unneeded copy - return comper_pair(e1.key(), e1.value()) < comper_pair(e2.key(), e2.value()); // Using pair comper - }); - - CHECK(diffs.size() == 1); // Note the change here, was 2 - } - -#ifdef JSON_HAS_CPP_17 - SECTION("issue #1292 - Serializing std::variant causes stack overflow") - { - static_assert( - !std::is_constructible>::value, ""); - } -#endif - - SECTION("issue #1299 - compile error in from_json converting to container " - "with std::pair") - { - json j = - { - {"1", {{"a", "testa_1"}, {"b", "testb_1"}}}, - {"2", {{"a", "testa_2"}, {"b", "testb_2"}}}, - {"3", {{"a", "testa_3"}, {"b", "testb_3"}}}, - }; - - std::map expected - { - {"1", {"testa_1", "testb_1"}}, - {"2", {"testa_2", "testb_2"}}, - {"3", {"testa_3", "testb_3"}}, - }; - const auto data = j.get(); - CHECK(expected == data); - } - - SECTION("issue #1445 - buffer overflow in dumping invalid utf-8 strings") - { - SECTION("a bunch of -1, ensure_ascii=true") - { - const auto length = 300; - - json dump_test; - dump_test["1"] = std::string(length, -1); - - std::string expected = "{\"1\":\""; - for (int i = 0; i < length; ++i) - { - expected += "\\ufffd"; - } - expected += "\"}"; - - auto s = dump_test.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace); - CHECK(s == expected); - } - SECTION("a bunch of -2, ensure_ascii=false") - { - const auto length = 500; - - json dump_test; - dump_test["1"] = std::string(length, -2); - - std::string expected = "{\"1\":\""; - for (int i = 0; i < length; ++i) - { - expected += "\xEF\xBF\xBD"; - } - expected += "\"}"; - - auto s = dump_test.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace); - CHECK(s == expected); - } - SECTION("test case in issue #1445") - { - nlohmann::json dump_test; - const int data[] = - { - 109, 108, 103, 125, -122, -53, 115, - 18, 3, 0, 102, 19, 1, 15, - -110, 13, -3, -1, -81, 32, 2, - 0, 0, 0, 0, 0, 0, 0, - 8, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, -80, 2, - 0, 0, 96, -118, 46, -116, 46, - 109, -84, -87, 108, 14, 109, -24, - -83, 13, -18, -51, -83, -52, -115, - 14, 6, 32, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - 64, 3, 0, 0, 0, 35, -74, - -73, 55, 57, -128, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - 0, 0, 33, 0, 0, 0, -96, - -54, -28, -26 - }; - std::string s; - for (unsigned i = 0; i < sizeof(data) / sizeof(int); i++) - { - s += static_cast(data[i]); - } - dump_test["1"] = s; - dump_test.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace); - } - } - - SECTION("issue #1447 - Integer Overflow (OSS-Fuzz 12506)") - { - json j = json::parse("[-9223372036854775808]"); - CHECK(j.dump() == "[-9223372036854775808]"); - } - - SECTION("issue #1708 - minimum value of int64_t can be outputted") - { - constexpr auto smallest = (std::numeric_limits::min)(); - json j = smallest; - CHECK(j.dump() == std::to_string(smallest)); - } - - SECTION("issue #1727 - Contains with non-const lvalue json_pointer picks the wrong overload") - { - json j = {{"root", {{"settings", {{"logging", true}}}}}}; - - auto jptr1 = "/root/settings/logging"_json_pointer; - auto jptr2 = json::json_pointer{"/root/settings/logging"}; - - CHECK(j.contains(jptr1)); - CHECK(j.contains(jptr2)); - } - - SECTION("issue #1647 - compile error when deserializing enum if both non-default from_json and non-member operator== exists for other type") - { - { - json j; - NonDefaultFromJsonStruct x(j); - NonDefaultFromJsonStruct y; - CHECK(x == y); - } - - auto val = nlohmann::json("one").get(); - CHECK(val == for_1647::one); - json j = val; - } - - SECTION("issue #1715 - json::from_cbor does not respect allow_exceptions = false when input is string literal") - { - SECTION("string literal") - { - json cbor = json::from_cbor("B", true, false); - CHECK(cbor.is_discarded()); - } - - SECTION("string array") - { - const char input[] = { 'B', 0x00 }; - json cbor = json::from_cbor(input, true, false); - CHECK(cbor.is_discarded()); - } - - SECTION("std::string") - { - json cbor = json::from_cbor(std::string("B"), true, false); - CHECK(cbor.is_discarded()); - } - } - - SECTION("issue #1805 - A pair is json constructible only if T1 and T2 are json constructible") - { - static_assert(!std::is_constructible>::value, ""); - static_assert(!std::is_constructible>::value, ""); - static_assert(std::is_constructible>::value, ""); - } - SECTION("issue #1825 - A tuple is json constructible only if all T in Args are json constructible") - { - static_assert(!std::is_constructible>::value, ""); - static_assert(!std::is_constructible>::value, ""); - static_assert(std::is_constructible>::value, ""); - } - - SECTION("issue #1983 - JSON patch diff for op=add formation is not as per standard (RFC 6902)") - { - const auto source = R"({ "foo": [ "1", "2" ] })"_json; - const auto target = R"({"foo": [ "1", "2", "3" ]})"_json; - const auto result = json::diff(source, target); - CHECK(result.dump() == R"([{"op":"add","path":"/foo/-","value":"3"}])"); - } - - SECTION("issue #2067 - cannot serialize binary data to text JSON") - { - const unsigned char data[] = {0x81, 0xA4, 0x64, 0x61, 0x74, 0x61, 0xC4, 0x0F, 0x33, 0x30, 0x30, 0x32, 0x33, 0x34, 0x30, 0x31, 0x30, 0x37, 0x30, 0x35, 0x30, 0x31, 0x30}; - json j = json::from_msgpack(data, sizeof(data) / sizeof(data[0])); - CHECK_NOTHROW( - j.dump(4, // Indent - ' ', // Indent char - false, // Ensure ascii - json::error_handler_t::strict // Error - ) - ); - } - - SECTION("PR #2181 - regression bug with lvalue") - { - // see https://github.com/nlohmann/json/pull/2181#issuecomment-653326060 - json j{{"x", "test"}}; - std::string defval = "default value"; - auto val = j.value("x", defval); - auto val2 = j.value("y", defval); - } - - SECTION("issue #2293 - eof doesnt cause parsing to stop") - { - std::vector data = - { - 0x7B, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x20, 0x4F, 0x42 - }; - json result = json::from_cbor(data, true, false); - CHECK(result.is_discarded()); - } } #if !defined(JSON_NOEXCEPTION) diff --git a/test/src/unit-regression2.cpp b/test/src/unit-regression2.cpp new file mode 100644 index 000000000..d445f068e --- /dev/null +++ b/test/src/unit-regression2.cpp @@ -0,0 +1,481 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ (test suite) +| | |__ | | | | | | version 3.9.0 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2019 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "doctest_compatibility.h" +DOCTEST_GCC_SUPPRESS_WARNING("-Wfloat-equal") + +// for some reason including this after the json header leads to linker errors with VS 2017... +#include + +#define private public +#include +using nlohmann::json; +#undef private + +#include +#include +#include +#include +#include + +#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 +#endif + +#ifdef JSON_HAS_CPP_17 + #include +#endif + +///////////////////////////////////////////////////////////////////// +// for #1021 +///////////////////////////////////////////////////////////////////// + +using float_json = nlohmann::basic_json; + +///////////////////////////////////////////////////////////////////// +// for #1647 +///////////////////////////////////////////////////////////////////// +namespace +{ +struct NonDefaultFromJsonStruct { }; + +inline bool operator== (NonDefaultFromJsonStruct const&, NonDefaultFromJsonStruct const&) +{ + return true; +} + +enum class for_1647 { one, two }; + +NLOHMANN_JSON_SERIALIZE_ENUM(for_1647, +{ + {for_1647::one, "one"}, + {for_1647::two, "two"}, +}) +} + +///////////////////////////////////////////////////////////////////// +// for #1299 +///////////////////////////////////////////////////////////////////// + +struct Data +{ + Data() = default; + Data(const std::string& a_, const std::string b_) : a(a_), b(b_) {} + std::string a {}; + std::string b {}; +}; + +void from_json(const json& j, Data& data) +{ + j["a"].get_to(data.a); + j["b"].get_to(data.b); +} + +bool operator==(Data const& lhs, Data const& rhs) +{ + return lhs.a == rhs.a && lhs.b == rhs.b; +} + +//bool operator!=(Data const& lhs, Data const& rhs) +//{ +// return !(lhs == rhs); +//} + +namespace nlohmann +{ +template <> +struct adl_serializer +{ + static NonDefaultFromJsonStruct from_json (json const&) noexcept + { + return {}; + } +}; +} + +///////////////////////////////////////////////////////////////////// +// for #1805 +///////////////////////////////////////////////////////////////////// + +struct NotSerializableData +{ + int mydata; + float myfloat; +}; + + +TEST_CASE("regression tests 2") +{ + SECTION("issue #1001 - Fix memory leak during parser callback") + { + auto geojsonExample = R"( + { "type": "FeatureCollection", + "features": [ + { "type": "Feature", + "geometry": {"type": "Point", "coordinates": [102.0, 0.5]}, + "properties": {"prop0": "value0"} + }, + { "type": "Feature", + "geometry": { + "type": "LineString", + "coordinates": [ + [102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0] + ] + }, + "properties": { + "prop0": "value0", + "prop1": 0.0 + } + }, + { "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], + [100.0, 1.0], [100.0, 0.0] ] + ] + }, + "properties": { + "prop0": "value0", + "prop1": {"this": "that"} + } + } + ] + })"; + + json::parser_callback_t cb = [&](int, json::parse_event_t event, json & parsed) + { + // skip uninteresting events + if (event == json::parse_event_t::value && !parsed.is_primitive()) + { + return false; + } + + switch (event) + { + case json::parse_event_t::key: + { + return true; + } + case json::parse_event_t::value: + { + return false; + } + case json::parse_event_t::object_start: + { + return true; + } + case json::parse_event_t::object_end: + { + return false; + } + case json::parse_event_t::array_start: + { + return true; + } + case json::parse_event_t::array_end: + { + return false; + } + + default: + { + return true; + } + } + }; + + auto j = json::parse(geojsonExample, cb, true); + CHECK(j == json()); + } + + SECTION("issue #1021 - to/from_msgpack only works with standard typization") + { + float_json j = 1000.0; + CHECK(float_json::from_cbor(float_json::to_cbor(j)) == j); + CHECK(float_json::from_msgpack(float_json::to_msgpack(j)) == j); + CHECK(float_json::from_ubjson(float_json::to_ubjson(j)) == j); + + float_json j2 = {1000.0, 2000.0, 3000.0}; + CHECK(float_json::from_ubjson(float_json::to_ubjson(j2, true, true)) == j2); + } + + SECTION("issue #1045 - Using STL algorithms with JSON containers with expected results?") + { + json diffs = nlohmann::json::array(); + json m1{{"key1", 42}}; + json m2{{"key2", 42}}; + auto p1 = m1.items(); + auto p2 = m2.items(); + + using it_type = decltype(p1.begin()); + + std::set_difference( + p1.begin(), p1.end(), + p2.begin(), p2.end(), + std::inserter(diffs, diffs.end()), [&](const it_type & e1, const it_type & e2) -> bool + { + using comper_pair = std::pair; // Trying to avoid unneeded copy + return comper_pair(e1.key(), e1.value()) < comper_pair(e2.key(), e2.value()); // Using pair comper + }); + + CHECK(diffs.size() == 1); // Note the change here, was 2 + } + +#ifdef JSON_HAS_CPP_17 + SECTION("issue #1292 - Serializing std::variant causes stack overflow") + { + static_assert( + !std::is_constructible>::value, ""); + } +#endif + + SECTION("issue #1299 - compile error in from_json converting to container " + "with std::pair") + { + json j = + { + {"1", {{"a", "testa_1"}, {"b", "testb_1"}}}, + {"2", {{"a", "testa_2"}, {"b", "testb_2"}}}, + {"3", {{"a", "testa_3"}, {"b", "testb_3"}}}, + }; + + std::map expected + { + {"1", {"testa_1", "testb_1"}}, + {"2", {"testa_2", "testb_2"}}, + {"3", {"testa_3", "testb_3"}}, + }; + const auto data = j.get(); + CHECK(expected == data); + } + + SECTION("issue #1445 - buffer overflow in dumping invalid utf-8 strings") + { + SECTION("a bunch of -1, ensure_ascii=true") + { + const auto length = 300; + + json dump_test; + dump_test["1"] = std::string(length, -1); + + std::string expected = "{\"1\":\""; + for (int i = 0; i < length; ++i) + { + expected += "\\ufffd"; + } + expected += "\"}"; + + auto s = dump_test.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace); + CHECK(s == expected); + } + SECTION("a bunch of -2, ensure_ascii=false") + { + const auto length = 500; + + json dump_test; + dump_test["1"] = std::string(length, -2); + + std::string expected = "{\"1\":\""; + for (int i = 0; i < length; ++i) + { + expected += "\xEF\xBF\xBD"; + } + expected += "\"}"; + + auto s = dump_test.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace); + CHECK(s == expected); + } + SECTION("test case in issue #1445") + { + nlohmann::json dump_test; + const int data[] = + { + 109, 108, 103, 125, -122, -53, 115, + 18, 3, 0, 102, 19, 1, 15, + -110, 13, -3, -1, -81, 32, 2, + 0, 0, 0, 0, 0, 0, 0, + 8, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, -80, 2, + 0, 0, 96, -118, 46, -116, 46, + 109, -84, -87, 108, 14, 109, -24, + -83, 13, -18, -51, -83, -52, -115, + 14, 6, 32, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 64, 3, 0, 0, 0, 35, -74, + -73, 55, 57, -128, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 33, 0, 0, 0, -96, + -54, -28, -26 + }; + std::string s; + for (unsigned i = 0; i < sizeof(data) / sizeof(int); i++) + { + s += static_cast(data[i]); + } + dump_test["1"] = s; + dump_test.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace); + } + } + + SECTION("issue #1447 - Integer Overflow (OSS-Fuzz 12506)") + { + json j = json::parse("[-9223372036854775808]"); + CHECK(j.dump() == "[-9223372036854775808]"); + } + + SECTION("issue #1708 - minimum value of int64_t can be outputted") + { + constexpr auto smallest = (std::numeric_limits::min)(); + json j = smallest; + CHECK(j.dump() == std::to_string(smallest)); + } + + SECTION("issue #1727 - Contains with non-const lvalue json_pointer picks the wrong overload") + { + json j = {{"root", {{"settings", {{"logging", true}}}}}}; + + auto jptr1 = "/root/settings/logging"_json_pointer; + auto jptr2 = json::json_pointer{"/root/settings/logging"}; + + CHECK(j.contains(jptr1)); + CHECK(j.contains(jptr2)); + } + + SECTION("issue #1647 - compile error when deserializing enum if both non-default from_json and non-member operator== exists for other type") + { + { + json j; + NonDefaultFromJsonStruct x(j); + NonDefaultFromJsonStruct y; + CHECK(x == y); + } + + auto val = nlohmann::json("one").get(); + CHECK(val == for_1647::one); + json j = val; + } + + SECTION("issue #1715 - json::from_cbor does not respect allow_exceptions = false when input is string literal") + { + SECTION("string literal") + { + json cbor = json::from_cbor("B", true, false); + CHECK(cbor.is_discarded()); + } + + SECTION("string array") + { + const char input[] = { 'B', 0x00 }; + json cbor = json::from_cbor(input, true, false); + CHECK(cbor.is_discarded()); + } + + SECTION("std::string") + { + json cbor = json::from_cbor(std::string("B"), true, false); + CHECK(cbor.is_discarded()); + } + } + + SECTION("issue #1805 - A pair is json constructible only if T1 and T2 are json constructible") + { + static_assert(!std::is_constructible>::value, ""); + static_assert(!std::is_constructible>::value, ""); + static_assert(std::is_constructible>::value, ""); + } + SECTION("issue #1825 - A tuple is json constructible only if all T in Args are json constructible") + { + static_assert(!std::is_constructible>::value, ""); + static_assert(!std::is_constructible>::value, ""); + static_assert(std::is_constructible>::value, ""); + } + + SECTION("issue #1983 - JSON patch diff for op=add formation is not as per standard (RFC 6902)") + { + const auto source = R"({ "foo": [ "1", "2" ] })"_json; + const auto target = R"({"foo": [ "1", "2", "3" ]})"_json; + const auto result = json::diff(source, target); + CHECK(result.dump() == R"([{"op":"add","path":"/foo/-","value":"3"}])"); + } + + SECTION("issue #2067 - cannot serialize binary data to text JSON") + { + const unsigned char data[] = {0x81, 0xA4, 0x64, 0x61, 0x74, 0x61, 0xC4, 0x0F, 0x33, 0x30, 0x30, 0x32, 0x33, 0x34, 0x30, 0x31, 0x30, 0x37, 0x30, 0x35, 0x30, 0x31, 0x30}; + json j = json::from_msgpack(data, sizeof(data) / sizeof(data[0])); + CHECK_NOTHROW( + j.dump(4, // Indent + ' ', // Indent char + false, // Ensure ascii + json::error_handler_t::strict // Error + ) + ); + } + + SECTION("PR #2181 - regression bug with lvalue") + { + // see https://github.com/nlohmann/json/pull/2181#issuecomment-653326060 + json j{{"x", "test"}}; + std::string defval = "default value"; + auto val = j.value("x", defval); + auto val2 = j.value("y", defval); + } + + SECTION("issue #2293 - eof doesnt cause parsing to stop") + { + std::vector data = + { + 0x7B, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x20, 0x4F, 0x42 + }; + json result = json::from_cbor(data, true, false); + CHECK(result.is_discarded()); + } + + SECTION("issue #2315 - json.update and vectordoes not work with ordered_json") + { + nlohmann::ordered_json jsonAnimals = {{"animal", "dog"}}; + nlohmann::ordered_json jsonCat = {{"animal", "cat"}}; + jsonAnimals.update(jsonCat); + CHECK(jsonAnimals["animal"] == "cat"); + + auto jsonAnimals_parsed = nlohmann::ordered_json::parse(jsonAnimals.dump()); + CHECK(jsonAnimals == jsonAnimals_parsed); + + std::vector> intData = {std::make_pair("aaaa", 11), + std::make_pair("bbb", 222) + }; + nlohmann::ordered_json jsonObj; + for (const auto& data : intData) + { + jsonObj[data.first] = data.second; + } + CHECK(jsonObj["aaaa"] == 11); + CHECK(jsonObj["bbb"] == 222); + } +}