mirror of
https://github.com/nlohmann/json.git
synced 2026-10-10 00:17:13 +00:00
Two changes roughly halve the CPU time needed to build the unit tests (clang 21: ~300 s -> ~150 s, GCC 16: ~750-820 s -> ~395-420 s, Debug): - Move the C++14/17/20-dependent tests into separate unit-<name>-cpp<N>.cpp files. A test file is built for every standard whose JSON_HAS_CPP_<N> macro it mentions, so far whole large files were rebuilt for C++14/17/20 because of a few #ifdef sections. The main files are now built for C++11 only; the ci_test_*_cxx<N> jobs still build every file for every standard. - Add the CMake option JSON_TestUnityBuild (ON by default, OFF with MinGW): compatible test files are compiled in batches as one translation unit so they share the template instantiations of the library. Each file keeps its own CTest test, which runs the batch executable filtered to that file's test cases. The binary-format tests form an explicit group; the rest is batched by JSON_TestUnityBatchSize. Fix the name clashes that merging files exposed, document the rules for test files in tests/README.md, and point CONTRIBUTING.md to it. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
1233 lines
42 KiB
C++
1233 lines
42 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_update(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_update(depth, variant));
|
|
json result = json::parse(nested_objects_update(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_update(depth, 0));
|
|
target.update(json::parse(nested_objects_update(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_update(depth, 0));
|
|
const json expected = j;
|
|
j.update(j, true);
|
|
CHECK(j == expected);
|
|
}
|
|
}
|
|
}
|