| // __ _____ _____ _____ | |
| // __| | __| | | | JSON for Modern C++ (supporting code) | |
| // | | |__ | | | | | | version 3.12.0 | |
| // |_____|_____|_____|_|___| https://github.com/nlohmann/json | |
| // | |
| // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me> | |
| // SPDX-License-Identifier: MIT | |
| #include "doctest_compatibility.h" | |
| #include <nlohmann/json.hpp> | |
| using nlohmann::json; | |
| #include <string> | |
| namespace | |
| { | |
| // update(source, true) as documented, written recursively; only usable on | |
| // values nested a few hundred levels deep | |
| void reference_update(json& target, const json& source) | |
| { | |
| for (auto it = source.begin(); it != source.end(); ++it) | |
| { | |
| const auto existing = target.find(it.key()); | |
| if (it.value().is_object() && existing != target.end() && existing->is_object()) | |
| { | |
| reference_update(*existing, it.value()); | |
| } | |
| else | |
| { | |
| target[it.key()] = it.value(); | |
| } | |
| } | |
| } | |
| // objects nested `depth` levels deep under the key "a", with members that | |
| // differ by `variant` on the way down | |
| std::string nested_objects(const std::size_t depth, const int variant) | |
| { | |
| std::string text; | |
| for (std::size_t i = 0; i < depth; ++i) | |
| { | |
| text += "{"; | |
| if ((i + static_cast<std::size_t>(variant)) % 3 == 0) | |
| { | |
| text += "\"s" + std::to_string(variant) + "\":" + std::to_string(i) + ","; | |
| } | |
| if (variant == 2 && i % 5 == 0) | |
| { | |
| // an object replacing a primitive, which is not merged | |
| text += R"("s0":{"o":1},)"; | |
| } | |
| text += "\"a\":"; | |
| } | |
| text += variant == 1 ? "{\"x\":1}" : "{\"y\":2}"; | |
| text.append(depth, '}'); | |
| return text; | |
| } | |
| } // namespace | |
| TEST_CASE("modifiers") | |
| { | |
| SECTION("clear()") | |
| { | |
| SECTION("boolean") | |
| { | |
| json j = true; | |
| json const k = j; | |
| j.clear(); | |
| CHECK(j == json(json::value_t::boolean)); | |
| CHECK(j == json(k.type())); | |
| } | |
| SECTION("string") | |
| { | |
| json j = "hello world"; | |
| json const k = j; | |
| j.clear(); | |
| CHECK(j == json(json::value_t::string)); | |
| CHECK(j == json(k.type())); | |
| } | |
| SECTION("array") | |
| { | |
| SECTION("empty array") | |
| { | |
| json j = json::array(); | |
| json const k = j; | |
| j.clear(); | |
| CHECK(j.empty()); | |
| CHECK(j == json(json::value_t::array)); | |
| CHECK(j == json(k.type())); | |
| } | |
| SECTION("filled array") | |
| { | |
| json j = {1, 2, 3}; | |
| json const k = j; | |
| j.clear(); | |
| CHECK(j.empty()); | |
| CHECK(j == json(json::value_t::array)); | |
| CHECK(j == json(k.type())); | |
| } | |
| } | |
| SECTION("object") | |
| { | |
| SECTION("empty object") | |
| { | |
| json j = json::object(); | |
| json const k = j; | |
| j.clear(); | |
| CHECK(j.empty()); | |
| CHECK(j == json(json::value_t::object)); | |
| CHECK(j == json(k.type())); | |
| } | |
| SECTION("filled object") | |
| { | |
| json j = {{"one", 1}, {"two", 2}, {"three", 3}}; | |
| json const k = j; | |
| j.clear(); | |
| CHECK(j.empty()); | |
| CHECK(j == json(json::value_t::object)); | |
| CHECK(j == json(k.type())); | |
| } | |
| } | |
| SECTION("binary") | |
| { | |
| SECTION("empty binary") | |
| { | |
| json j = json::binary({}); | |
| json const k = j; | |
| j.clear(); | |
| CHECK(!j.empty()); | |
| CHECK(j == json(json::value_t::binary)); | |
| CHECK(j == json(k.type())); | |
| } | |
| SECTION("filled binary") | |
| { | |
| json j = json::binary({1, 2, 3, 4, 5}); | |
| json const k = j; | |
| j.clear(); | |
| CHECK(!j.empty()); | |
| CHECK(j == json(json::value_t::binary)); | |
| CHECK(j == json(k.type())); | |
| } | |
| SECTION("filled binary with subtype") | |
| { | |
| json j = json::binary({1, 2, 3, 4, 5}, 42); | |
| json const k = j; | |
| j.clear(); | |
| CHECK(!j.empty()); | |
| CHECK(!j.get_binary().has_subtype()); | |
| CHECK(j == json(json::value_t::binary)); | |
| CHECK(j == json(k.type())); | |
| } | |
| } | |
| SECTION("number (integer)") | |
| { | |
| json j = 23; | |
| json const k = j; | |
| j.clear(); | |
| CHECK(j == json(json::value_t::number_integer)); | |
| CHECK(j == json(k.type())); | |
| } | |
| SECTION("number (unsigned)") | |
| { | |
| json j = 23u; | |
| json const k = j; | |
| j.clear(); | |
| CHECK(j == json(json::value_t::number_integer)); | |
| CHECK(j == json(k.type())); | |
| } | |
| SECTION("number (float)") | |
| { | |
| json j = 23.42; | |
| json const k = j; | |
| j.clear(); | |
| CHECK(j == json(json::value_t::number_float)); | |
| CHECK(j == json(k.type())); | |
| } | |
| SECTION("null") | |
| { | |
| json j = nullptr; | |
| json const k = j; | |
| j.clear(); | |
| CHECK(j == json(json::value_t::null)); | |
| CHECK(j == json(k.type())); | |
| } | |
| } | |
| SECTION("push_back()") | |
| { | |
| SECTION("to array") | |
| { | |
| SECTION("json&&") | |
| { | |
| SECTION("null") | |
| { | |
| json j; | |
| j.push_back(1); | |
| j.push_back(2); | |
| CHECK(j.type() == json::value_t::array); | |
| CHECK(j == json({1, 2})); | |
| } | |
| SECTION("array") | |
| { | |
| json j = {1, 2, 3}; | |
| j.push_back("Hello"); | |
| CHECK(j.type() == json::value_t::array); | |
| CHECK(j == json({1, 2, 3, "Hello"})); | |
| } | |
| SECTION("other type") | |
| { | |
| json j = 1; | |
| CHECK_THROWS_WITH_AS(j.push_back("Hello"), "[json.exception.type_error.308] cannot use push_back() with number", json::type_error&); | |
| } | |
| } | |
| SECTION("const json&") | |
| { | |
| SECTION("null") | |
| { | |
| json j; | |
| json const k(1); | |
| j.push_back(k); | |
| j.push_back(k); | |
| CHECK(j.type() == json::value_t::array); | |
| CHECK(j == json({1, 1})); | |
| } | |
| SECTION("array") | |
| { | |
| json j = {1, 2, 3}; | |
| json const k("Hello"); | |
| j.push_back(k); | |
| CHECK(j.type() == json::value_t::array); | |
| CHECK(j == json({1, 2, 3, "Hello"})); | |
| } | |
| SECTION("other type") | |
| { | |
| json j = 1; | |
| json const k("Hello"); | |
| CHECK_THROWS_WITH_AS(j.push_back(k), "[json.exception.type_error.308] cannot use push_back() with number", json::type_error&); | |
| } | |
| } | |
| } | |
| SECTION("to object") | |
| { | |
| SECTION("null") | |
| { | |
| json j; | |
| j.push_back(json::object_t::value_type({"one", 1})); | |
| j.push_back(json::object_t::value_type({"two", 2})); | |
| CHECK(j.type() == json::value_t::object); | |
| CHECK(j.size() == 2); | |
| CHECK(j["one"] == json(1)); | |
| CHECK(j["two"] == json(2)); | |
| } | |
| SECTION("object") | |
| { | |
| json j(json::value_t::object); | |
| j.push_back(json::object_t::value_type({"one", 1})); | |
| j.push_back(json::object_t::value_type({"two", 2})); | |
| CHECK(j.size() == 2); | |
| CHECK(j["one"] == json(1)); | |
| CHECK(j["two"] == json(2)); | |
| } | |
| SECTION("other type") | |
| { | |
| json j = 1; | |
| json const k("Hello"); | |
| CHECK_THROWS_WITH_AS(j.push_back(json::object_t::value_type({"one", 1})), "[json.exception.type_error.308] cannot use push_back() with number", json::type_error&); | |
| } | |
| } | |
| 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"}})); | |
| // invalid values (no string/val pair) | |
| CHECK_THROWS_WITH_AS(j.push_back({1}), "[json.exception.type_error.308] cannot use push_back() with object", json::type_error&); | |
| CHECK_THROWS_WITH_AS(j.push_back({1, 2}), "[json.exception.type_error.308] cannot use push_back() with object", json::type_error&); | |
| CHECK_THROWS_WITH_AS(j.push_back({1, 2, 3, 4}), "[json.exception.type_error.308] cannot use push_back() with object", json::type_error&); | |
| } | |
| } | |
| } | |
| SECTION("emplace_back()") | |
| { | |
| SECTION("to array") | |
| { | |
| SECTION("null") | |
| { | |
| json j; | |
| auto& x1 = j.emplace_back(1); | |
| CHECK(x1 == 1); | |
| auto& x2 = j.emplace_back(2); | |
| CHECK(x2 == 2); | |
| CHECK(j.type() == json::value_t::array); | |
| CHECK(j == json({1, 2})); | |
| } | |
| SECTION("array") | |
| { | |
| json j = {1, 2, 3}; | |
| auto& x = j.emplace_back("Hello"); | |
| CHECK(x == "Hello"); | |
| CHECK(j.type() == json::value_t::array); | |
| CHECK(j == json({1, 2, 3, "Hello"})); | |
| } | |
| SECTION("multiple values") | |
| { | |
| json j; | |
| auto& x = j.emplace_back(3, "foo"); | |
| CHECK(x == json({"foo", "foo", "foo"})); | |
| CHECK(j.type() == json::value_t::array); | |
| CHECK(j == json({{"foo", "foo", "foo"}})); | |
| } | |
| } | |
| SECTION("other type") | |
| { | |
| json j = 1; | |
| CHECK_THROWS_WITH_AS(j.emplace_back("Hello"), "[json.exception.type_error.311] cannot use emplace_back() with number", json::type_error&); | |
| } | |
| } | |
| SECTION("emplace()") | |
| { | |
| SECTION("to object") | |
| { | |
| SECTION("null") | |
| { | |
| // start with a null value | |
| json j; | |
| // add a new key | |
| auto res1 = j.emplace("foo", "bar"); | |
| CHECK(res1.second == true); | |
| CHECK(*res1.first == "bar"); | |
| // the null value is changed to an object | |
| CHECK(j.type() == json::value_t::object); | |
| // add a new key | |
| auto res2 = j.emplace("baz", "bam"); | |
| CHECK(res2.second == true); | |
| CHECK(*res2.first == "bam"); | |
| // we try to insert at given key - no change | |
| auto res3 = j.emplace("baz", "bad"); | |
| CHECK(res3.second == false); | |
| CHECK(*res3.first == "bam"); | |
| // the final object | |
| CHECK(j == json({{"baz", "bam"}, {"foo", "bar"}})); | |
| } | |
| SECTION("object") | |
| { | |
| // start with an object | |
| json j = {{"foo", "bar"}}; | |
| // add a new key | |
| auto res1 = j.emplace("baz", "bam"); | |
| CHECK(res1.second == true); | |
| CHECK(*res1.first == "bam"); | |
| // add an existing key | |
| auto res2 = j.emplace("foo", "bad"); | |
| CHECK(res2.second == false); | |
| CHECK(*res2.first == "bar"); | |
| // check final object | |
| CHECK(j == json({{"baz", "bam"}, {"foo", "bar"}})); | |
| } | |
| } | |
| SECTION("other type") | |
| { | |
| json j = 1; | |
| CHECK_THROWS_WITH_AS(j.emplace("foo", "bar"), "[json.exception.type_error.311] cannot use emplace() with number", json::type_error&); | |
| } | |
| } | |
| SECTION("operator+=") | |
| { | |
| SECTION("to array") | |
| { | |
| SECTION("json&&") | |
| { | |
| SECTION("null") | |
| { | |
| json j; | |
| j += 1; | |
| j += 2; | |
| CHECK(j.type() == json::value_t::array); | |
| CHECK(j == json({1, 2})); | |
| } | |
| SECTION("array") | |
| { | |
| json j = {1, 2, 3}; | |
| j += "Hello"; | |
| CHECK(j.type() == json::value_t::array); | |
| CHECK(j == json({1, 2, 3, "Hello"})); | |
| } | |
| SECTION("other type") | |
| { | |
| json j = 1; | |
| CHECK_THROWS_WITH_AS(j += "Hello", "[json.exception.type_error.308] cannot use push_back() with number", json::type_error&); | |
| } | |
| } | |
| SECTION("const json&") | |
| { | |
| SECTION("null") | |
| { | |
| json j; | |
| json const k(1); | |
| j += k; | |
| j += k; | |
| CHECK(j.type() == json::value_t::array); | |
| CHECK(j == json({1, 1})); | |
| } | |
| SECTION("array") | |
| { | |
| json j = {1, 2, 3}; | |
| json const k("Hello"); | |
| j += k; | |
| CHECK(j.type() == json::value_t::array); | |
| CHECK(j == json({1, 2, 3, "Hello"})); | |
| } | |
| SECTION("other type") | |
| { | |
| json j = 1; | |
| json const k("Hello"); | |
| CHECK_THROWS_WITH_AS(j += k, "[json.exception.type_error.308] cannot use push_back() with number", json::type_error&); | |
| } | |
| } | |
| } | |
| SECTION("to object") | |
| { | |
| SECTION("null") | |
| { | |
| json j; | |
| j += json::object_t::value_type({"one", 1}); | |
| j += json::object_t::value_type({"two", 2}); | |
| CHECK(j.type() == json::value_t::object); | |
| CHECK(j.size() == 2); | |
| CHECK(j["one"] == json(1)); | |
| CHECK(j["two"] == json(2)); | |
| } | |
| SECTION("object") | |
| { | |
| json j(json::value_t::object); | |
| j += json::object_t::value_type({"one", 1}); | |
| j += json::object_t::value_type({"two", 2}); | |
| CHECK(j.size() == 2); | |
| CHECK(j["one"] == json(1)); | |
| CHECK(j["two"] == json(2)); | |
| } | |
| SECTION("other type") | |
| { | |
| json j = 1; | |
| json const k("Hello"); | |
| CHECK_THROWS_WITH_AS(j += json::object_t::value_type({"one", 1}), "[json.exception.type_error.308] cannot use push_back() with number", json::type_error&); | |
| } | |
| } | |
| 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_WITH_AS((k += {1, 2, 3, 4}), "[json.exception.type_error.308] cannot use push_back() with object", json::type_error&); | |
| } | |
| } | |
| } | |
| SECTION("insert()") | |
| { | |
| json j_array = {1, 2, 3, 4}; | |
| json j_value = 5; | |
| SECTION("value at position") | |
| { | |
| SECTION("insert before begin()") | |
| { | |
| auto it = j_array.insert(j_array.begin(), j_value); | |
| CHECK(j_array.size() == 5); | |
| CHECK(*it == j_value); | |
| CHECK(j_array.begin() == it); | |
| CHECK(j_array == json({5, 1, 2, 3, 4})); | |
| } | |
| SECTION("insert in the middle") | |
| { | |
| auto it = j_array.insert(j_array.begin() + 2, j_value); | |
| CHECK(j_array.size() == 5); | |
| CHECK(*it == j_value); | |
| CHECK((it - j_array.begin()) == 2); | |
| CHECK(j_array == json({1, 2, 5, 3, 4})); | |
| } | |
| SECTION("insert before end()") | |
| { | |
| auto it = j_array.insert(j_array.end(), j_value); | |
| CHECK(j_array.size() == 5); | |
| CHECK(*it == j_value); | |
| CHECK((j_array.end() - it) == 1); | |
| CHECK(j_array == json({1, 2, 3, 4, 5})); | |
| } | |
| } | |
| SECTION("rvalue at position") | |
| { | |
| SECTION("insert before begin()") | |
| { | |
| auto it = j_array.insert(j_array.begin(), 5); | |
| CHECK(j_array.size() == 5); | |
| CHECK(*it == j_value); | |
| CHECK(j_array.begin() == it); | |
| CHECK(j_array == json({5, 1, 2, 3, 4})); | |
| } | |
| SECTION("insert in the middle") | |
| { | |
| auto it = j_array.insert(j_array.begin() + 2, 5); | |
| CHECK(j_array.size() == 5); | |
| CHECK(*it == j_value); | |
| CHECK((it - j_array.begin()) == 2); | |
| CHECK(j_array == json({1, 2, 5, 3, 4})); | |
| } | |
| SECTION("insert before end()") | |
| { | |
| auto it = j_array.insert(j_array.end(), 5); | |
| CHECK(j_array.size() == 5); | |
| CHECK(*it == j_value); | |
| CHECK((j_array.end() - it) == 1); | |
| CHECK(j_array == json({1, 2, 3, 4, 5})); | |
| } | |
| } | |
| SECTION("rvalue at position moves rather than copies") | |
| { | |
| // regression test: insert(pos, basic_json&&) used to forward to | |
| // insert(pos, const basic_json&) because the named rvalue | |
| // reference parameter is itself an lvalue, so it always | |
| // deep-copied its argument instead of moving it | |
| json j_big = std::string(1000, 'x'); | |
| const auto* const original_buffer = j_big.get_ref<const std::string&>().data(); | |
| auto it = j_array.insert(j_array.begin(), std::move(j_big)); | |
| CHECK(j_array.size() == 5); | |
| CHECK(*it == json(std::string(1000, 'x'))); | |
| CHECK((*it).get_ref<const std::string&>().data() == original_buffer); | |
| // the moved-from value is null, the same as after push_back(&&) | |
| CHECK(j_big.is_null()); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) | |
| } | |
| SECTION("self-aliasing insertion") | |
| { | |
| SECTION("without reallocation") | |
| { | |
| json j_self = {1, 2, 3, 4}; | |
| j_self.get_ref<json::array_t&>().reserve(j_self.size() + 1); | |
| auto it = j_self.insert(j_self.begin(), std::move(j_self[1])); | |
| CHECK(j_self.size() == 5); | |
| CHECK(*it == json(2)); | |
| CHECK(j_self == json({2, 1, nullptr, 3, 4})); | |
| } | |
| SECTION("with reallocation") | |
| { | |
| json j_self = {1, 2, 3, 4}; | |
| j_self.get_ref<json::array_t&>().shrink_to_fit(); | |
| auto it = j_self.insert(j_self.begin(), std::move(j_self[1])); | |
| CHECK(j_self.size() == 5); | |
| CHECK(*it == json(2)); | |
| CHECK(j_self == json({2, 1, nullptr, 3, 4})); | |
| } | |
| } | |
| SECTION("copies at position") | |
| { | |
| SECTION("insert before begin()") | |
| { | |
| auto it = j_array.insert(j_array.begin(), 3, 5); | |
| CHECK(j_array.size() == 7); | |
| CHECK(*it == j_value); | |
| CHECK(j_array.begin() == it); | |
| CHECK(j_array == json({5, 5, 5, 1, 2, 3, 4})); | |
| } | |
| SECTION("insert in the middle") | |
| { | |
| auto it = j_array.insert(j_array.begin() + 2, 3, 5); | |
| CHECK(j_array.size() == 7); | |
| CHECK(*it == j_value); | |
| CHECK((it - j_array.begin()) == 2); | |
| CHECK(j_array == json({1, 2, 5, 5, 5, 3, 4})); | |
| } | |
| SECTION("insert before end()") | |
| { | |
| auto it = j_array.insert(j_array.end(), 3, 5); | |
| CHECK(j_array.size() == 7); | |
| CHECK(*it == j_value); | |
| CHECK((j_array.end() - it) == 3); | |
| CHECK(j_array == json({1, 2, 3, 4, 5, 5, 5})); | |
| } | |
| SECTION("insert nothing (count = 0)") | |
| { | |
| auto it = j_array.insert(j_array.end(), 0, 5); | |
| CHECK(j_array.size() == 4); | |
| // the returned iterator points to the first inserted element; | |
| // there were 4 elements, so it should point to the 5th | |
| CHECK(it == j_array.begin() + 4); | |
| CHECK(j_array == json({1, 2, 3, 4})); | |
| } | |
| } | |
| SECTION("range for array") | |
| { | |
| json j_other_array = {"first", "second"}; | |
| SECTION("proper usage") | |
| { | |
| auto it = j_array.insert(j_array.end(), j_other_array.begin(), j_other_array.end()); | |
| CHECK(j_array.size() == 6); | |
| CHECK(*it == *j_other_array.begin()); | |
| CHECK((j_array.end() - it) == 2); | |
| CHECK(j_array == json({1, 2, 3, 4, "first", "second"})); | |
| } | |
| SECTION("empty range") | |
| { | |
| auto it = j_array.insert(j_array.end(), j_other_array.begin(), j_other_array.begin()); | |
| CHECK(j_array.size() == 4); | |
| CHECK(it == j_array.end()); | |
| CHECK(j_array == json({1, 2, 3, 4})); | |
| } | |
| SECTION("invalid iterators") | |
| { | |
| json j_other_array2 = {"first", "second"}; | |
| CHECK_THROWS_WITH_AS(j_array.insert(j_array.end(), j_array.begin(), j_array.end()), "[json.exception.invalid_iterator.211] passed iterators may not belong to container", | |
| json::invalid_iterator&); | |
| CHECK_THROWS_WITH_AS(j_array.insert(j_array.end(), j_other_array.begin(), j_other_array2.end()), "[json.exception.invalid_iterator.210] iterators do not fit", | |
| json::invalid_iterator&); | |
| } | |
| SECTION("iterators not pointing into an array") | |
| { | |
| json j_object2 = {{"k", 1}, {"l", 2}}; | |
| json j_primitive = 5; | |
| json j_null; | |
| CHECK_THROWS_WITH_AS(j_array.insert(j_array.begin(), j_object2.begin(), j_object2.end()), "[json.exception.invalid_iterator.202] iterators first and last must point to arrays", | |
| json::invalid_iterator&); | |
| CHECK_THROWS_WITH_AS(j_array.insert(j_array.begin(), j_primitive.begin(), j_primitive.end()), "[json.exception.invalid_iterator.202] iterators first and last must point to arrays", | |
| json::invalid_iterator&); | |
| CHECK_THROWS_WITH_AS(j_array.insert(j_array.begin(), j_null.begin(), j_null.end()), "[json.exception.invalid_iterator.202] iterators first and last must point to arrays", | |
| json::invalid_iterator&); | |
| } | |
| } | |
| SECTION("range for object") | |
| { | |
| json j_object1 = {{"one", "eins"}, {"two", "zwei"}}; | |
| json j_object2 = {{"eleven", "elf"}, {"seventeen", "siebzehn"}}; | |
| SECTION("proper usage") | |
| { | |
| j_object1.insert(j_object2.begin(), j_object2.end()); | |
| CHECK(j_object1.size() == 4); | |
| } | |
| SECTION("empty range") | |
| { | |
| j_object1.insert(j_object2.begin(), j_object2.begin()); | |
| CHECK(j_object1.size() == 2); | |
| } | |
| SECTION("invalid iterators") | |
| { | |
| json const j_other_array2 = {"first", "second"}; | |
| CHECK_THROWS_WITH_AS(j_array.insert(j_object2.begin(), j_object2.end()), "[json.exception.type_error.309] cannot use insert() with array", json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_object1.insert(j_object1.begin(), j_object2.end()), "[json.exception.invalid_iterator.210] iterators do not fit", json::invalid_iterator&); | |
| CHECK_THROWS_WITH_AS(j_object1.insert(j_array.begin(), j_array.end()), "[json.exception.invalid_iterator.202] iterators first and last must point to objects", json::invalid_iterator&); | |
| } | |
| } | |
| SECTION("initializer list at position") | |
| { | |
| SECTION("insert before begin()") | |
| { | |
| auto it = j_array.insert(j_array.begin(), {7, 8, 9}); | |
| CHECK(j_array.size() == 7); | |
| CHECK(*it == json(7)); | |
| CHECK(j_array.begin() == it); | |
| CHECK(j_array == json({7, 8, 9, 1, 2, 3, 4})); | |
| } | |
| SECTION("insert in the middle") | |
| { | |
| auto it = j_array.insert(j_array.begin() + 2, {7, 8, 9}); | |
| CHECK(j_array.size() == 7); | |
| CHECK(*it == json(7)); | |
| CHECK((it - j_array.begin()) == 2); | |
| CHECK(j_array == json({1, 2, 7, 8, 9, 3, 4})); | |
| } | |
| SECTION("insert before end()") | |
| { | |
| auto it = j_array.insert(j_array.end(), {7, 8, 9}); | |
| CHECK(j_array.size() == 7); | |
| CHECK(*it == json(7)); | |
| CHECK((j_array.end() - it) == 3); | |
| CHECK(j_array == json({1, 2, 3, 4, 7, 8, 9})); | |
| } | |
| } | |
| SECTION("initializer list referring to the array's own elements (#5656)") | |
| { | |
| SECTION("sufficient capacity (no reallocation)") | |
| { | |
| json j_own = json::array(); | |
| j_own.get_ref<json::array_t&>().reserve(8); | |
| j_own.push_back("a"); | |
| j_own.push_back("b"); | |
| j_own.push_back("c"); | |
| const json& j_own_cref = j_own; | |
| auto it = j_own.insert(j_own.begin(), {j_own_cref[0], j_own_cref[1]}); | |
| CHECK(*it == json("a")); | |
| CHECK(j_own == json({"a", "b", "a", "b", "c"})); | |
| } | |
| SECTION("insufficient capacity (reallocation)") | |
| { | |
| json j_own = {"a", "b", "c"}; | |
| j_own.get_ref<json::array_t&>().shrink_to_fit(); | |
| const json& j_own_cref = j_own; | |
| auto it = j_own.insert(j_own.begin(), {j_own_cref[2]}); | |
| CHECK(*it == json("c")); | |
| CHECK(j_own == json({"c", "a", "b", "c"})); | |
| } | |
| } | |
| SECTION("invalid iterator") | |
| { | |
| // pass iterator to a different array | |
| json j_another_array = {1, 2}; | |
| json j_yet_another_array = {"first", "second"}; | |
| CHECK_THROWS_WITH_AS(j_array.insert(j_another_array.end(), 10), "[json.exception.invalid_iterator.202] iterator does not fit current value", json::invalid_iterator&); | |
| CHECK_THROWS_WITH_AS(j_array.insert(j_another_array.end(), j_value), "[json.exception.invalid_iterator.202] iterator does not fit current value", json::invalid_iterator&); | |
| CHECK_THROWS_WITH_AS(j_array.insert(j_another_array.end(), 10, 11), "[json.exception.invalid_iterator.202] iterator does not fit current value", json::invalid_iterator&); | |
| CHECK_THROWS_WITH_AS(j_array.insert(j_another_array.end(), j_yet_another_array.begin(), j_yet_another_array.end()), "[json.exception.invalid_iterator.202] iterator does not fit current value", json::invalid_iterator&); | |
| CHECK_THROWS_WITH_AS(j_array.insert(j_another_array.end(), {1, 2, 3, 4}), "[json.exception.invalid_iterator.202] iterator does not fit current value", json::invalid_iterator&); | |
| } | |
| SECTION("non-array type") | |
| { | |
| // call insert on a non-array type | |
| json j_nonarray = 3; | |
| json j_yet_another_array = {"first", "second"}; | |
| CHECK_THROWS_WITH_AS(j_nonarray.insert(j_nonarray.end(), 10), "[json.exception.type_error.309] cannot use insert() with number", json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_nonarray.insert(j_nonarray.end(), j_value), "[json.exception.type_error.309] cannot use insert() with number", json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_nonarray.insert(j_nonarray.end(), 10, 11), "[json.exception.type_error.309] cannot use insert() with number", json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_nonarray.insert(j_nonarray.end(), j_yet_another_array.begin(), j_yet_another_array.end()), "[json.exception.type_error.309] cannot use insert() with number", json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_nonarray.insert(j_nonarray.end(), {1, 2, 3, 4}), "[json.exception.type_error.309] cannot use insert() with number", json::type_error&); | |
| } | |
| } | |
| SECTION("update()") | |
| { | |
| SECTION("non-recursive (default)") | |
| { | |
| json j_object1 = {{"one", "eins"}, {"two", "zwei"}}; | |
| json j_object2 = {{"three", "drei"}, {"two", "zwo"}}; | |
| json j_array = {1, 2, 3, 4}; | |
| SECTION("const reference") | |
| { | |
| SECTION("proper usage") | |
| { | |
| j_object1.update(j_object2); | |
| CHECK(j_object1 == json({{"one", "eins"}, {"two", "zwo"}, {"three", "drei"}})); | |
| json j_null; | |
| j_null.update(j_object2); | |
| CHECK(j_null == j_object2); | |
| } | |
| SECTION("wrong types") | |
| { | |
| CHECK_THROWS_WITH_AS(j_array.update(j_object1), "[json.exception.type_error.312] cannot use update() with array", json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_object1.update(j_array), "[json.exception.type_error.312] cannot use update() with array", json::type_error&); | |
| } | |
| } | |
| SECTION("iterator range") | |
| { | |
| SECTION("proper usage") | |
| { | |
| j_object1.update(j_object2.begin(), j_object2.end()); | |
| CHECK(j_object1 == json({{"one", "eins"}, {"two", "zwo"}, {"three", "drei"}})); | |
| json j_null; | |
| j_null.update(j_object2.begin(), j_object2.end()); | |
| CHECK(j_null == j_object2); | |
| } | |
| SECTION("empty range") | |
| { | |
| j_object1.update(j_object2.begin(), j_object2.begin()); | |
| CHECK(j_object1 == json({{"one", "eins"}, {"two", "zwei"}})); | |
| } | |
| SECTION("invalid iterators") | |
| { | |
| json const j_other_array2 = {"first", "second"}; | |
| CHECK_THROWS_WITH_AS(j_array.update(j_object2.begin(), j_object2.end()), "[json.exception.type_error.312] cannot use update() with array", json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_object1.update(j_object1.begin(), j_object2.end()), "[json.exception.invalid_iterator.210] iterators do not fit", json::invalid_iterator&); | |
| CHECK_THROWS_WITH_AS(j_object1.update(j_array.begin(), j_array.end()), "[json.exception.type_error.312] cannot use update() with array", json::type_error&); | |
| } | |
| } | |
| } | |
| SECTION("recursive") | |
| { | |
| SECTION("const reference") | |
| { | |
| SECTION("extend object") | |
| { | |
| json j1 = {{"string", "s"}, {"numbers", {{"one", 1}}}}; | |
| json const j2 = {{"string", "t"}, {"numbers", {{"two", 2}}}}; | |
| j1.update(j2, true); | |
| CHECK(j1 == json({{"string", "t"}, {"numbers", {{"one", 1}, {"two", 2}}}})); | |
| } | |
| SECTION("replace object") | |
| { | |
| json j1 = {{"string", "s"}, {"numbers", {{"one", 1}}}}; | |
| json const j2 = {{"string", "t"}, {"numbers", 1}}; | |
| j1.update(j2, true); | |
| CHECK(j1 == json({{"string", "t"}, {"numbers", 1}})); | |
| } | |
| SECTION("overwrite primitive with object") | |
| { | |
| json j1 = {{"k", 1}}; | |
| json const j2 = {{"k", {{"x", 2}}}}; | |
| j1.update(j2, true); | |
| CHECK(j1 == json({{"k", {{"x", 2}}}})); | |
| } | |
| SECTION("overwrite array with object") | |
| { | |
| json j1 = {{"k", {1, 2}}}; | |
| json const j2 = {{"k", {{"x", 2}}}}; | |
| j1.update(j2, true); | |
| CHECK(j1 == json({{"k", {{"x", 2}}}})); | |
| } | |
| SECTION("overwrite nested primitive with object") | |
| { | |
| json j1 = {{"k", {{"inner", 1}}}}; | |
| json const j2 = {{"k", {{"inner", {{"x", 2}}}}}}; | |
| j1.update(j2, true); | |
| CHECK(j1 == json({{"k", {{"inner", {{"x", 2}}}}}})); | |
| } | |
| } | |
| } | |
| } | |
| SECTION("swap()") | |
| { | |
| SECTION("json") | |
| { | |
| SECTION("member swap") | |
| { | |
| json j("hello world"); | |
| json k(42.23); | |
| j.swap(k); | |
| CHECK(j == json(42.23)); | |
| CHECK(k == json("hello world")); | |
| } | |
| SECTION("nonmember swap") | |
| { | |
| json j("hello world"); | |
| json k(42.23); | |
| using std::swap; | |
| swap(j, k); | |
| CHECK(j == json(42.23)); | |
| CHECK(k == json("hello world")); | |
| } | |
| } | |
| SECTION("array_t") | |
| { | |
| SECTION("array_t type") | |
| { | |
| json j = {1, 2, 3, 4}; | |
| json::array_t a = {"foo", "bar", "baz"}; | |
| j.swap(a); | |
| CHECK(j == json({"foo", "bar", "baz"})); | |
| j.swap(a); | |
| CHECK(j == json({1, 2, 3, 4})); | |
| } | |
| SECTION("non-array_t type") | |
| { | |
| json j = 17; | |
| json::array_t a = {"foo", "bar", "baz"}; | |
| CHECK_THROWS_WITH_AS(j.swap(a), "[json.exception.type_error.310] cannot use swap(array_t&) with number", json::type_error&); | |
| } | |
| } | |
| SECTION("object_t") | |
| { | |
| SECTION("object_t type") | |
| { | |
| json j = {{"one", 1}, {"two", 2}}; | |
| json::object_t o = {{"cow", "Kuh"}, {"chicken", "Huhn"}}; | |
| j.swap(o); | |
| CHECK(j == json({{"cow", "Kuh"}, {"chicken", "Huhn"}})); | |
| j.swap(o); | |
| CHECK(j == json({{"one", 1}, {"two", 2}})); | |
| } | |
| SECTION("non-object_t type") | |
| { | |
| json j = 17; | |
| json::object_t o = {{"cow", "Kuh"}, {"chicken", "Huhn"}}; | |
| CHECK_THROWS_WITH_AS(j.swap(o), "[json.exception.type_error.310] cannot use swap(object_t&) with number", json::type_error&); | |
| } | |
| } | |
| SECTION("string_t") | |
| { | |
| SECTION("string_t type") | |
| { | |
| json j = "Hello world"; | |
| json::string_t s = "Hallo Welt"; | |
| j.swap(s); | |
| CHECK(j == json("Hallo Welt")); | |
| j.swap(s); | |
| CHECK(j == json("Hello world")); | |
| } | |
| SECTION("non-string_t type") | |
| { | |
| json j = 17; | |
| json::string_t s = "Hallo Welt"; | |
| CHECK_THROWS_WITH_AS(j.swap(s), "[json.exception.type_error.310] cannot use swap(string_t&) with number", json::type_error&); | |
| } | |
| } | |
| SECTION("binary_t") | |
| { | |
| SECTION("binary_t type") | |
| { | |
| json j = json::binary({1, 2, 3, 4}); | |
| json::binary_t s = {{5, 6, 7, 8}}; | |
| j.swap(s); | |
| CHECK(j == json::binary({5, 6, 7, 8})); | |
| j.swap(s); | |
| CHECK(j == json::binary({1, 2, 3, 4})); | |
| } | |
| SECTION("binary_t::container_type type") | |
| { | |
| json j = json::binary({1, 2, 3, 4}); | |
| std::vector<std::uint8_t> s = {{5, 6, 7, 8}}; | |
| j.swap(s); | |
| CHECK(j == json::binary({5, 6, 7, 8})); | |
| j.swap(s); | |
| CHECK(j == json::binary({1, 2, 3, 4})); | |
| } | |
| SECTION("non-binary_t type") | |
| { | |
| json j = 17; | |
| json::binary_t s1 = {{1, 2, 3, 4}}; | |
| std::vector<std::uint8_t> s2 = {{5, 6, 7, 8}}; | |
| CHECK_THROWS_WITH_AS(j.swap(s1), "[json.exception.type_error.310] cannot use swap(binary_t&) with number", json::type_error); | |
| CHECK_THROWS_WITH_AS(j.swap(s2), "[json.exception.type_error.310] cannot use swap(binary_t::container_type&) with number", json::type_error); | |
| } | |
| } | |
| } | |
| } | |
| TEST_CASE("update() on deeply nested values") | |
| { | |
| SECTION("merging past the descent bound gives the same result") | |
| { | |
| // every depth on either side of where the iterative version takes | |
| // over (detail::recursion_depth_limit(), 128) | |
| for (std::size_t depth = 0; depth <= 300; ++depth) | |
| { | |
| CAPTURE(depth) | |
| for (int variant = 0; variant < 3; ++variant) | |
| { | |
| CAPTURE(variant) | |
| const json source = json::parse(nested_objects(depth, variant)); | |
| json result = json::parse(nested_objects(depth, (variant + 1) % 3)); | |
| json expected = result; | |
| result.update(source, true); | |
| reference_update(expected, source); | |
| CHECK(result == expected); | |
| } | |
| } | |
| } | |
| SECTION("objects nested too deeply for the call stack (#5545)") | |
| { | |
| // merging used to recurse once per nesting level. The result is only | |
| // walked, never copied or compared, since those recurse too. | |
| const std::size_t depth = 100000; | |
| json target = json::parse(nested_objects(depth, 0)); | |
| target.update(json::parse(nested_objects(depth, 1)), true); | |
| const json* p = ⌖ | |
| for (std::size_t i = 0; i < depth; ++i) | |
| { | |
| p = &p->at("a"); | |
| } | |
| CHECK(p->size() == 2); | |
| CHECK(p->at("x") == 1); | |
| CHECK(p->at("y") == 2); | |
| } | |
| } | |
| TEST_CASE("update() with an argument that aliases *this (#5641)") | |
| { | |
| #if !defined(JSON_NOEXCEPTION) // checks which exception is thrown, and that nothing changed | |
| SECTION("the target is checked before the argument, as before the copy") | |
| { | |
| json j = 1; | |
| CHECK_THROWS_WITH_AS(j.update(json::array()), "[json.exception.type_error.312] cannot use update() with number", json::type_error&); | |
| CHECK_THROWS_WITH_AS(j.update(j.cbegin(), j.cend()), "[json.exception.type_error.312] cannot use update() with number", json::type_error&); | |
| json k; | |
| CHECK_THROWS_WITH_AS(k.update(json::array()), "[json.exception.type_error.312] cannot use update() with array", json::type_error&); | |
| CHECK(k == json::object()); | |
| } | |
| #endif | |
| SECTION("const reference") | |
| { | |
| SECTION("j.update(j[\"a\"]): assigning into the argument's parent destroys it mid-iteration") | |
| { | |
| // reproduces issue #5641, case 3 | |
| json j = {{"a", {{"a", 1}, {"b", 2}}}}; | |
| j.update(j["a"]); | |
| CHECK(j == json({{"a", 1}, {"b", 2}})); | |
| } | |
| SECTION("merge_objects with an argument that is a member of *this") | |
| { | |
| json j = {{"defaults", {{"opts", {{"a", 1}}}}}, {"opts", {{"b", 2}}}}; | |
| j.update(j["defaults"], true); | |
| CHECK(j == json({{"defaults", {{"opts", {{"a", 1}}}}}, {"opts", {{"a", 1}, {"b", 2}}}})); | |
| } | |
| SECTION("ordered_json: inserting a new key relocates the vector behind the argument") | |
| { | |
| // reproduces issue #5641, case 4 | |
| using nlohmann::ordered_json; | |
| ordered_json j = {{"a", {{"x", 1}, {"y", 2}, {"z", 3}}}}; | |
| j.update(j["a"]); | |
| CHECK(j == ordered_json({{"a", {{"x", 1}, {"y", 2}, {"z", 3}}}, {"x", 1}, {"y", 2}, {"z", 3}})); | |
| } | |
| } | |
| SECTION("iterator range") | |
| { | |
| SECTION("range that is a member of *this") | |
| { | |
| json j = {{"a", {{"a", 1}, {"b", 2}}}}; | |
| j.update(j["a"].begin(), j["a"].end()); | |
| CHECK(j == json({{"a", 1}, {"b", 2}})); | |
| } | |
| } | |
| SECTION("nested past the iterative descent bound aliases *this") | |
| { | |
| // every depth on either side of where the iterative version takes | |
| // over (detail::recursion_depth_limit(), 128); merging *this into | |
| // itself is idempotent, aliased or not | |
| for (const std::size_t depth : | |
| { | |
| std::size_t{0}, std::size_t{127}, std::size_t{128}, std::size_t{300} | |
| }) | |
| { | |
| CAPTURE(depth) | |
| json j = json::parse(nested_objects(depth, 0)); | |
| const json expected = j; | |
| j.update(j, true); | |
| CHECK(j == expected); | |
| } | |
| } | |
| } |