Files
json/tests/src/unit-modifiers.cpp
T
Niels Lohmann 519094151e Merge deeply nested objects without recursing per nesting level
merge_patch() and update(j, true) merged a nested object by calling
themselves on it, once per nesting level. A value nested deeply enough -
50,000 levels of objects on an 8 MiB stack - exhausted the call stack
and terminated the process, although parse() accepts such values without
complaint.

Bound the descent the same way dump() does. The recursion now carries
the nesting level, and once merge_depth_limit() (128) levels have been
entered, update_members_iteratively() and merge_patch_iteratively()
finish the merge on an explicit stack. They still merge a nested object
completely before the next member, and in the same order, so the results,
including the parents JSON_DIAGNOSTICS reports paths from, are unchanged.
Values nested less deeply than the bound run the same code as before, so
the common case does not pay for the stack: merging only on it cost
10-14% in a first version.

The public signatures are unchanged. The recursive worker behind
merge_patch() has its own name rather than being a private overload, so
that &basic_json::merge_patch stays unambiguous.

Tests check every depth up to 300 against recursive reference
implementations of both operations, check the diagnostic paths past the
bound, and merge objects nested 100,000 levels deep.

Fixes #5545 for update(j, true), and #5393 for merge_patch().

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-24 17:11:39 +02:00

1079 lines
36 KiB
C++

// __ _____ _____ _____
// __| | __| | | | 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 += "\"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("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("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("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 (basic_json::merge_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 = &target;
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);
}
}