mirror of
https://github.com/nlohmann/json.git
synced 2026-10-01 13:34:14 +07:00
basic_json::swap() (and the friend swap() and the pre-C++20 std::swap overload that forward to it) only exchanged m_data.m_type/m_data.m_value, leaving each value's json_base_class_t subobject in place. This is inconsistent with the copy and move constructors and copy assignment, which all carry the base class along with the value, so after a.swap(b) any metadata stored in a CustomBaseClass ended up attached to the wrong value. Algorithms that mix swap() with moves, such as std::sort, scrambled the metadata across the whole container. Fix the member swap() to also exchange the json_base_class_t subobject and extend the noexcept specifications of swap() and the friend swap() accordingly. Fixes #5653. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
408 lines
11 KiB
C++
408 lines
11 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 <algorithm>
|
|
#include <set>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "doctest_compatibility.h"
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
// Test extending nlohmann::json by using a custom base class.
|
|
// Add some metadata to each node and test the behaviour of copy / move
|
|
template<class MetaDataType>
|
|
class json_metadata
|
|
{
|
|
public:
|
|
using metadata_t = MetaDataType;
|
|
metadata_t& metadata()
|
|
{
|
|
return m_metadata;
|
|
}
|
|
const metadata_t& metadata() const
|
|
{
|
|
return m_metadata;
|
|
}
|
|
private:
|
|
metadata_t m_metadata = {};
|
|
};
|
|
|
|
template<class T>
|
|
using json_with_metadata =
|
|
nlohmann::basic_json <
|
|
std::map,
|
|
std::vector,
|
|
std::string,
|
|
bool,
|
|
std::int64_t,
|
|
std::uint64_t,
|
|
double,
|
|
std::allocator,
|
|
nlohmann::adl_serializer,
|
|
std::vector<std::uint8_t>,
|
|
json_metadata<T>
|
|
>;
|
|
|
|
TEST_CASE("JSON Node Metadata")
|
|
{
|
|
SECTION("type int")
|
|
{
|
|
using json = json_with_metadata<int>;
|
|
json null;
|
|
auto obj = json::object();
|
|
auto array = json::array();
|
|
|
|
null.metadata() = 1;
|
|
obj.metadata() = 2;
|
|
array.metadata() = 3;
|
|
auto copy = array;
|
|
|
|
CHECK(null.metadata() == 1);
|
|
CHECK(obj.metadata() == 2);
|
|
CHECK(array.metadata() == 3);
|
|
CHECK(copy.metadata() == 3);
|
|
}
|
|
SECTION("type vector<int>")
|
|
{
|
|
using json = json_with_metadata<std::vector<int>>;
|
|
json value;
|
|
value.metadata().emplace_back(1);
|
|
auto copy = value;
|
|
value.metadata().emplace_back(2);
|
|
|
|
CHECK(copy.metadata().size() == 1);
|
|
CHECK(copy.metadata().at(0) == 1);
|
|
CHECK(value.metadata().size() == 2);
|
|
CHECK(value.metadata().at(0) == 1);
|
|
CHECK(value.metadata().at(1) == 2);
|
|
}
|
|
SECTION("copy ctor")
|
|
{
|
|
using json = json_with_metadata<std::vector<int>>;
|
|
json value;
|
|
value.metadata().emplace_back(1);
|
|
value.metadata().emplace_back(2);
|
|
|
|
json copy = value;
|
|
|
|
CHECK(copy.metadata().size() == 2);
|
|
CHECK(copy.metadata().at(0) == 1);
|
|
CHECK(copy.metadata().at(1) == 2);
|
|
CHECK(value.metadata().size() == 2);
|
|
CHECK(value.metadata().at(0) == 1);
|
|
CHECK(value.metadata().at(1) == 2);
|
|
|
|
value.metadata().clear();
|
|
CHECK(copy.metadata().size() == 2);
|
|
CHECK(value.metadata().size() == 0);
|
|
}
|
|
SECTION("move ctor")
|
|
{
|
|
using json = json_with_metadata<std::vector<int>>;
|
|
json value;
|
|
value.metadata().emplace_back(1);
|
|
value.metadata().emplace_back(2);
|
|
|
|
const json moved = std::move(value);
|
|
|
|
CHECK(moved.metadata().size() == 2);
|
|
CHECK(moved.metadata().at(0) == 1);
|
|
CHECK(moved.metadata().at(1) == 2);
|
|
}
|
|
SECTION("move assign")
|
|
{
|
|
using json = json_with_metadata<std::vector<int>>;
|
|
json value;
|
|
value.metadata().emplace_back(1);
|
|
value.metadata().emplace_back(2);
|
|
|
|
json moved;
|
|
moved = std::move(value);
|
|
|
|
CHECK(moved.metadata().size() == 2);
|
|
CHECK(moved.metadata().at(0) == 1);
|
|
CHECK(moved.metadata().at(1) == 2);
|
|
}
|
|
SECTION("copy assign")
|
|
{
|
|
using json = json_with_metadata<std::vector<int>>;
|
|
json value;
|
|
value.metadata().emplace_back(1);
|
|
value.metadata().emplace_back(2);
|
|
|
|
json copy;
|
|
copy = value;
|
|
|
|
CHECK(copy.metadata().size() == 2);
|
|
CHECK(copy.metadata().at(0) == 1);
|
|
CHECK(copy.metadata().at(1) == 2);
|
|
CHECK(value.metadata().size() == 2);
|
|
CHECK(value.metadata().at(0) == 1);
|
|
CHECK(value.metadata().at(1) == 2);
|
|
|
|
value.metadata().clear();
|
|
CHECK(copy.metadata().size() == 2);
|
|
CHECK(value.metadata().size() == 0);
|
|
}
|
|
SECTION("type unique_ptr<int>")
|
|
{
|
|
using json = json_with_metadata<std::unique_ptr<int>>;
|
|
json value;
|
|
value.metadata().reset(new int(42)); // NOLINT(cppcoreguidelines-owning-memory)
|
|
auto moved = std::move(value);
|
|
|
|
CHECK(moved.metadata() != nullptr);
|
|
CHECK(*moved.metadata() == 42);
|
|
}
|
|
SECTION("type vector<int> in json array")
|
|
{
|
|
using json = json_with_metadata<std::vector<int>>;
|
|
json value;
|
|
value.metadata().emplace_back(1);
|
|
value.metadata().emplace_back(2);
|
|
|
|
json const array(10, value);
|
|
|
|
CHECK(value.metadata().size() == 2);
|
|
CHECK(value.metadata().at(0) == 1);
|
|
CHECK(value.metadata().at(1) == 2);
|
|
|
|
for (const auto& val : array)
|
|
{
|
|
CHECK(val.metadata().size() == 2);
|
|
CHECK(val.metadata().at(0) == 1);
|
|
CHECK(val.metadata().at(1) == 2);
|
|
}
|
|
}
|
|
SECTION("member swap")
|
|
{
|
|
using json = json_with_metadata<int>;
|
|
json a = 1;
|
|
a.metadata() = 100;
|
|
json b = 2;
|
|
b.metadata() = 200;
|
|
|
|
a.swap(b);
|
|
|
|
CHECK(a.get<int>() == 2);
|
|
CHECK(b.get<int>() == 1);
|
|
CHECK(a.metadata() == 200);
|
|
CHECK(b.metadata() == 100);
|
|
}
|
|
SECTION("nonmember swap")
|
|
{
|
|
using json = json_with_metadata<int>;
|
|
json a = 1;
|
|
a.metadata() = 100;
|
|
json b = 2;
|
|
b.metadata() = 200;
|
|
|
|
using std::swap;
|
|
swap(a, b);
|
|
|
|
CHECK(a.get<int>() == 2);
|
|
CHECK(b.get<int>() == 1);
|
|
CHECK(a.metadata() == 200);
|
|
CHECK(b.metadata() == 100);
|
|
}
|
|
SECTION("std::swap")
|
|
{
|
|
using json = json_with_metadata<int>;
|
|
json a = 1;
|
|
a.metadata() = 100;
|
|
json b = 2;
|
|
b.metadata() = 200;
|
|
|
|
std::swap(a, b);
|
|
|
|
CHECK(a.get<int>() == 2);
|
|
CHECK(b.get<int>() == 1);
|
|
CHECK(a.metadata() == 200);
|
|
CHECK(b.metadata() == 100);
|
|
}
|
|
SECTION("std::sort keeps metadata attached to its value")
|
|
{
|
|
// std::sort mixes swap() with moves; each value's metadata must
|
|
// travel with it, just as it does for copy, move, and assignment
|
|
using json = json_with_metadata<int>;
|
|
std::vector<json> values;
|
|
for (int v :
|
|
{
|
|
5, 3, 9, 1, 7, 2, 8, 4, 6, 0, 15, 13, 19, 11, 17, 12, 18, 14, 16, 10,
|
|
25, 23, 29, 21, 27, 22, 28, 24, 26, 20, 35, 33
|
|
})
|
|
{
|
|
json value = v;
|
|
value.metadata() = v;
|
|
values.push_back(value);
|
|
}
|
|
|
|
std::sort(values.begin(), values.end());
|
|
|
|
for (const auto& value : values)
|
|
{
|
|
CHECK(value.metadata() == value.get<int>());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test extending nlohmann::json by using a custom base class.
|
|
// Add a custom member function template iterating over the whole json tree.
|
|
class visitor_adaptor
|
|
{
|
|
public:
|
|
template <class Fnc>
|
|
void visit(const Fnc& fnc) const;
|
|
private:
|
|
template <class Ptr, class Fnc>
|
|
void do_visit(const Ptr& ptr, const Fnc& fnc) const;
|
|
};
|
|
|
|
using json_with_visitor_t = nlohmann::basic_json <
|
|
std::map,
|
|
std::vector,
|
|
std::string,
|
|
bool,
|
|
std::int64_t,
|
|
std::uint64_t,
|
|
double,
|
|
std::allocator,
|
|
nlohmann::adl_serializer,
|
|
std::vector<std::uint8_t>,
|
|
visitor_adaptor
|
|
>;
|
|
|
|
template <class Fnc>
|
|
void visitor_adaptor::visit(const Fnc& fnc) const
|
|
{
|
|
do_visit(json_with_visitor_t::json_pointer{}, fnc);
|
|
}
|
|
|
|
template <class Ptr, class Fnc>
|
|
void visitor_adaptor::do_visit(const Ptr& ptr, const Fnc& fnc) const
|
|
{
|
|
using value_t = nlohmann::detail::value_t;
|
|
const json_with_visitor_t& json = *static_cast<const json_with_visitor_t*>(this); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
|
|
switch (json.type())
|
|
{
|
|
case value_t::object:
|
|
for (const auto& entry : json.items())
|
|
{
|
|
entry.value().do_visit(ptr / entry.key(), fnc);
|
|
}
|
|
break;
|
|
case value_t::array:
|
|
for (std::size_t i = 0; i < json.size(); ++i)
|
|
{
|
|
json.at(i).do_visit(ptr / std::to_string(i), fnc);
|
|
}
|
|
break;
|
|
case value_t::discarded:
|
|
break;
|
|
case value_t::null:
|
|
case value_t::string:
|
|
case value_t::boolean:
|
|
case value_t::number_integer:
|
|
case value_t::number_unsigned:
|
|
case value_t::number_float:
|
|
case value_t::binary:
|
|
default:
|
|
fnc(ptr, json);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("JSON Visit Node")
|
|
{
|
|
json_with_visitor_t json;
|
|
json["null"];
|
|
json["int"] = -1;
|
|
json["uint"] = 1U;
|
|
json["float"] = 1.0;
|
|
json["boolean"] = true;
|
|
json["string"] = "string";
|
|
json["array"].push_back(0);
|
|
json["array"].push_back(1);
|
|
json["array"].push_back(json);
|
|
|
|
std::set<std::string> expected
|
|
{
|
|
"/null - null - null",
|
|
"/int - number_integer - -1",
|
|
"/uint - number_unsigned - 1",
|
|
"/float - number_float - 1.0",
|
|
"/boolean - boolean - true",
|
|
"/string - string - \"string\"",
|
|
"/array/0 - number_integer - 0",
|
|
"/array/1 - number_integer - 1",
|
|
|
|
"/array/2/null - null - null",
|
|
"/array/2/int - number_integer - -1",
|
|
"/array/2/uint - number_unsigned - 1",
|
|
"/array/2/float - number_float - 1.0",
|
|
"/array/2/boolean - boolean - true",
|
|
"/array/2/string - string - \"string\"",
|
|
"/array/2/array/0 - number_integer - 0",
|
|
"/array/2/array/1 - number_integer - 1"
|
|
};
|
|
|
|
json.visit(
|
|
[&](const json_with_visitor_t::json_pointer & p,
|
|
const json_with_visitor_t& j)
|
|
{
|
|
std::stringstream str;
|
|
str << p.to_string() << " - " ;
|
|
using value_t = nlohmann::detail::value_t;
|
|
switch (j.type())
|
|
{
|
|
case value_t::object:
|
|
str << "object";
|
|
break;
|
|
case value_t::array:
|
|
str << "array";
|
|
break;
|
|
case value_t::discarded:
|
|
str << "discarded";
|
|
break;
|
|
case value_t::null:
|
|
str << "null";
|
|
break;
|
|
case value_t::string:
|
|
str << "string";
|
|
break;
|
|
case value_t::boolean:
|
|
str << "boolean";
|
|
break;
|
|
case value_t::number_integer:
|
|
str << "number_integer";
|
|
break;
|
|
case value_t::number_unsigned:
|
|
str << "number_unsigned";
|
|
break;
|
|
case value_t::number_float:
|
|
str << "number_float";
|
|
break;
|
|
case value_t::binary:
|
|
str << "binary";
|
|
break;
|
|
default:
|
|
str << "error";
|
|
break;
|
|
}
|
|
str << " - " << j.dump();
|
|
CHECK(json.at(p) == j);
|
|
INFO(str.str());
|
|
CHECK(expected.count(str.str()) == 1);
|
|
expected.erase(str.str());
|
|
}
|
|
);
|
|
CHECK(expected.empty());
|
|
}
|