| // __ _____ _____ _____ | |
| // __| | __| | | | 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 <type_traits> | |
| #include <utility> | |
| #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::json::with_base_class_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 (const 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::json::with_base_class_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()); | |
| } | |
| // Test accessing members of a custom base class that are hidden by members of nlohmann::basic_json | |
| class base_class_with_hidden_members | |
| { | |
| public: | |
| const char* type_name() const noexcept // NOLINT(readability-convert-member-functions-to-static) | |
| { | |
| return "custom type_name"; | |
| } | |
| std::size_t size() const noexcept | |
| { | |
| return m_size; | |
| } | |
| std::size_t m_size = 42; | |
| }; | |
| using json_with_hidden_base_members = nlohmann::json::with_base_class_t<base_class_with_hidden_members>; | |
| TEST_CASE("JSON Node as_base_class") | |
| { | |
| using json = json_with_hidden_base_members; | |
| static_assert(std::is_same<decltype(std::declval<json&>().as_base_class()), json::json_base_class_t&>::value, ""); | |
| static_assert(std::is_same<decltype(std::declval<const json&>().as_base_class()), const json::json_base_class_t&>::value, ""); | |
| static_assert(noexcept(std::declval<json&>().as_base_class()), ""); | |
| static_assert(noexcept(std::declval<const json&>().as_base_class()), ""); | |
| SECTION("non-const") | |
| { | |
| json j = {1, 2, 3}; | |
| CHECK(std::string(j.type_name()) == "array"); | |
| CHECK(j.size() == 3); | |
| CHECK(std::string(j.as_base_class().type_name()) == "custom type_name"); | |
| CHECK(j.as_base_class().size() == 42); | |
| CHECK(&j.as_base_class() == &static_cast<json::json_base_class_t&>(j)); | |
| j.as_base_class().m_size = 7; | |
| CHECK(j.as_base_class().size() == 7); | |
| CHECK(j.size() == 3); | |
| } | |
| SECTION("const") | |
| { | |
| const json j = {1, 2, 3}; | |
| CHECK(std::string(j.type_name()) == "array"); | |
| CHECK(j.size() == 3); | |
| CHECK(std::string(j.as_base_class().type_name()) == "custom type_name"); | |
| CHECK(j.as_base_class().size() == 42); | |
| CHECK(&j.as_base_class() == &static_cast<const json::json_base_class_t&>(j)); | |
| } | |
| } | |
| // A custom base class with a const member: copy-constructible (initializing a | |
| // const member works fine), but not copy-/move-assignable (assigning one does | |
| // not). Used to check that copy construction never requires more than that. | |
| struct const_member_base | |
| { | |
| const int id = 7; // NOLINT(misc-non-private-member-variables-in-classes) | |
| }; | |
| using json_with_const_base = nlohmann::json::with_base_class_t<const_member_base>; | |
| // build an array nested @a depth levels deep, with the innermost value 1; | |
| // every level is constructed (never assigned), since const_member_base does | |
| // not support assignment | |
| static json_with_const_base make_nested_array(std::size_t depth) | |
| { | |
| if (depth == 0) | |
| { | |
| return json_with_const_base(1); // NOLINT(modernize-return-braced-init-list): {1} would be an array | |
| } | |
| return json_with_const_base::array({make_nested_array(depth - 1)}); | |
| } | |
| TEST_CASE("Regression test for issue #5674 - copy construction must not require an assignable base class") | |
| { | |
| SECTION("depth 0") | |
| { | |
| // as in the original bug report: copy construction only, no assignment | |
| const json_with_const_base j = {1, 2}; | |
| const json_with_const_base copy = j; // NOLINT(performance-unnecessary-copy-initialization) | |
| CHECK(copy.size() == 2); | |
| CHECK(copy.id == 7); | |
| } | |
| SECTION("nested deeper than the copy constructor's descent bound") | |
| { | |
| // beyond nesting_depth_limit() (128) levels, the copy constructor | |
| // copies without the call stack (copy_iteratively / copy_array_level), | |
| // which used to assign the base class of every element it created | |
| const std::size_t depth = 300; | |
| const json_with_const_base j = make_nested_array(depth); | |
| const json_with_const_base copy = j; // NOLINT(performance-unnecessary-copy-initialization) | |
| const json_with_const_base* c = © | |
| for (std::size_t level = 0; level <= depth; ++level) | |
| { | |
| CAPTURE(level) | |
| REQUIRE(c->id == 7); | |
| if (level < depth) | |
| { | |
| c = &c->at(0); | |
| } | |
| } | |
| CHECK(*c == 1); | |
| } | |
| } |