Allow to add a custom base class as an extension point to json nodes
* by default an empty class is used and the library behaves as it already did * if a user explicitly adds a base class (last template parameter) each node inherits it * this can be used to add custom extensions (e.g. add metadata / visitor methods) * add test for this feature
This commit is contained in:
parent
8fcdbf2e77
commit
143f868f9c
20
include/nlohmann/detail/json_custom_base_class.hpp
Normal file
20
include/nlohmann/detail/json_custom_base_class.hpp
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace nlohmann
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
|
||||
struct json_default_base {};
|
||||
|
||||
template<class T>
|
||||
using json_base_class = typename std::conditional<
|
||||
std::is_same<T, void>::value,
|
||||
json_default_base,
|
||||
T
|
||||
>::type;
|
||||
|
||||
} // namespace detail
|
||||
} // namespace nlohmann
|
||||
@ -240,12 +240,13 @@
|
||||
class NumberUnsignedType, class NumberFloatType, \
|
||||
template<typename> class AllocatorType, \
|
||||
template<typename, typename = void> class JSONSerializer, \
|
||||
class BinaryType>
|
||||
class BinaryType, \
|
||||
class CustomBaseClass>
|
||||
|
||||
#define NLOHMANN_BASIC_JSON_TPL \
|
||||
basic_json<ObjectType, ArrayType, StringType, BooleanType, \
|
||||
NumberIntegerType, NumberUnsignedType, NumberFloatType, \
|
||||
AllocatorType, JSONSerializer, BinaryType>
|
||||
AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>
|
||||
|
||||
// Macros to simplify conversion from/to types
|
||||
|
||||
|
||||
@ -47,6 +47,7 @@
|
||||
#include <nlohmann/detail/iterators/iteration_proxy.hpp>
|
||||
#include <nlohmann/detail/iterators/json_reverse_iterator.hpp>
|
||||
#include <nlohmann/detail/iterators/primitive_iterator.hpp>
|
||||
#include <nlohmann/detail/json_custom_base_class.hpp>
|
||||
#include <nlohmann/detail/json_pointer.hpp>
|
||||
#include <nlohmann/detail/json_ref.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
@ -93,6 +94,7 @@ The invariants are checked by member function assert_invariant().
|
||||
*/
|
||||
NLOHMANN_BASIC_JSON_TPL_DECLARATION
|
||||
class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions)
|
||||
: public ::nlohmann::detail::json_base_class<CustomBaseClass>
|
||||
{
|
||||
private:
|
||||
template<detail::value_t> friend struct detail::external_constructor;
|
||||
@ -119,6 +121,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
/// workaround type for MSVC
|
||||
using basic_json_t = NLOHMANN_BASIC_JSON_TPL;
|
||||
using json_base_class_t = ::nlohmann::detail::json_base_class<CustomBaseClass>;
|
||||
|
||||
JSON_PRIVATE_UNLESS_TESTED:
|
||||
// convenience aliases for types residing in namespace detail;
|
||||
@ -1132,7 +1135,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @brief copy constructor
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/basic_json/
|
||||
basic_json(const basic_json& other)
|
||||
: m_type(other.m_type)
|
||||
: json_base_class_t(other),
|
||||
m_type(other.m_type)
|
||||
{
|
||||
// check of passed value is valid
|
||||
other.assert_invariant();
|
||||
@ -1200,7 +1204,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @brief move constructor
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/basic_json/
|
||||
basic_json(basic_json&& other) noexcept
|
||||
: m_type(std::move(other.m_type)),
|
||||
: json_base_class_t(std::move(other)),
|
||||
m_type(std::move(other.m_type)),
|
||||
m_value(std::move(other.m_value))
|
||||
{
|
||||
// check that passed value is valid
|
||||
@ -1220,7 +1225,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
std::is_nothrow_move_constructible<value_t>::value&&
|
||||
std::is_nothrow_move_assignable<value_t>::value&&
|
||||
std::is_nothrow_move_constructible<json_value>::value&&
|
||||
std::is_nothrow_move_assignable<json_value>::value
|
||||
std::is_nothrow_move_assignable<json_value>::value&&
|
||||
std::is_nothrow_move_assignable<json_base_class_t>::value
|
||||
)
|
||||
{
|
||||
// check that passed value is valid
|
||||
@ -1229,6 +1235,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
using std::swap;
|
||||
swap(m_type, other.m_type);
|
||||
swap(m_value, other.m_value);
|
||||
json_base_class_t::operator=(std::move(other));
|
||||
|
||||
set_parents();
|
||||
assert_invariant();
|
||||
|
||||
@ -46,7 +46,8 @@ template<template<typename U, typename V, typename... Args> class ObjectType =
|
||||
template<typename U> class AllocatorType = std::allocator,
|
||||
template<typename T, typename SFINAE = void> class JSONSerializer =
|
||||
adl_serializer,
|
||||
class BinaryType = std::vector<std::uint8_t>>
|
||||
class BinaryType = std::vector<std::uint8_t>,
|
||||
class CustomBaseClass = void>
|
||||
class basic_json;
|
||||
|
||||
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
|
||||
|
||||
333
tests/src/unit-custom-base-class.cpp
Normal file
333
tests/src/unit-custom-base-class.cpp
Normal file
@ -0,0 +1,333 @@
|
||||
/*
|
||||
__ _____ _____ _____
|
||||
__| | __| | | | JSON for Modern C++ (test suite)
|
||||
| | |__ | | | | | | version 3.10.2
|
||||
|_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
|
||||
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
|
||||
SPDX-License-Identifier: MIT
|
||||
Copyright (c) 2013-2019 Niels Lohmann <http://nlohmann.me>.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
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);
|
||||
CHECK(value.metadata().size() == 0);
|
||||
}
|
||||
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);
|
||||
CHECK(value.metadata().size() == 0);
|
||||
}
|
||||
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);
|
||||
(*value.metadata()) = 42;
|
||||
auto moved = std::move(value);
|
||||
|
||||
CHECK(value.metadata() == nullptr);
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
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: fnc(ptr, json); break;
|
||||
case value_t::string: fnc(ptr, json); break;
|
||||
case value_t::boolean: fnc(ptr, json); break;
|
||||
case value_t::number_integer: fnc(ptr, json); break;
|
||||
case value_t::number_unsigned: fnc(ptr, json); break;
|
||||
case value_t::number_float: fnc(ptr, json); break;
|
||||
case value_t::binary: fnc(ptr, json); break;
|
||||
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 << " - " ;
|
||||
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);
|
||||
CHECK(expected.count(str.str()) == 1);
|
||||
expected.erase(str.str());
|
||||
}
|
||||
);
|
||||
CHECK(expected.empty());
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user