Skip to content

Commit

Permalink
Allow custom base class as node customization point (#3110)
Browse files Browse the repository at this point in the history
Co-authored-by: Niels Lohmann <niels.lohmann@gmail.com>
Co-authored-by: Florian Albrechtskirchinger <falbrechtskirchinger@gmail.com>
Co-authored-by: barcode <barcode@example.com>
  • Loading branch information
4 people authored Aug 28, 2022
1 parent f7973f4 commit bed648c
Show file tree
Hide file tree
Showing 12 changed files with 595 additions and 16 deletions.
88 changes: 88 additions & 0 deletions docs/examples/json_base_class_t.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#include <iostream>
#include <nlohmann/json.hpp>

class visitor_adaptor_with_metadata
{
public:
template <class Fnc>
void visit(const Fnc& fnc) const;

int metadata = 42;
private:
template <class Ptr, class Fnc>
void do_visit(const Ptr& ptr, const Fnc& fnc) const;
};

using json = 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_with_metadata
>;

template <class Fnc>
void visitor_adaptor_with_metadata::visit(const Fnc& fnc) const
{
do_visit(json::json_pointer{}, fnc);
}

template <class Ptr, class Fnc>
void visitor_adaptor_with_metadata::do_visit(const Ptr& ptr, const Fnc& fnc) const
{
using value_t = nlohmann::detail::value_t;
const json& j = *static_cast<const json*>(this);
switch (j.type())
{
case value_t::object:
fnc(ptr, j);
for (const auto& entry : j.items())
{
entry.value().do_visit(ptr / entry.key(), fnc);
}
break;
case value_t::array:
fnc(ptr, j);
for (std::size_t i = 0; i < j.size(); ++i)
{
j.at(i).do_visit(ptr / std::to_string(i), fnc);
}
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:
fnc(ptr, j);
break;
case value_t::discarded:
default:
break;
}
}

int main()
{
// create a json object
json j;
j["null"];
j["object"]["uint"] = 1U;
j["object"].metadata = 21;

// visit and output
j.visit(
[&](const json::json_pointer & p,
const json & j)
{
std::cout << (p.empty() ? std::string{"/"} : p.to_string())
<< " - metadata = " << j.metadata << " -> " << j.dump() << '\n';
});
}
4 changes: 4 additions & 0 deletions docs/examples/json_base_class_t.output
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/ - metadata = 42 -> {"null":null,"object":{"uint":1}}
/null - metadata = 42 -> null
/object - metadata = 21 -> {"uint":1}
/object/uint - metadata = 42 -> 1
4 changes: 3 additions & 1 deletion docs/mkdocs/docs/api/basic_json/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ template<
class NumberFloatType = double,
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;
```
Expand All @@ -32,6 +33,7 @@ class basic_json;
| `AllocatorType` | type of the allocator to use | |
| `JSONSerializer` | the serializer to resolve internal calls to `to_json()` and `from_json()` | [`json_serializer`](json_serializer.md) |
| `BinaryType` | type for binary arrays | [`binary_t`](binary_t.md) |
| `CustomBaseClass` | extension point for user code | [`json_base_class_t`](json_base_class_t.md) |

## Specializations

Expand Down
44 changes: 44 additions & 0 deletions docs/mkdocs/docs/api/basic_json/json_base_class_t.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# <small>nlohmann::basic_json::</small>json_base_class_t

```cpp
using json_base_class_t = detail::json_base_class<CustomBaseClass>;
```

The base class used to inject custom functionality into each instance of `basic_json`.
Examples of such functionality might be metadata, additional member functions (e.g., visitors), or other application-specific code.

## Template parameters

`CustomBaseClass`
: the base class to be added to `basic_json`

## Notes

#### Default type

The default value for `CustomBaseClass` is `void`. In this case an [empty base class](https://en.cppreference.com/w/cpp/language/ebo) is used and no additional functionality is injected.

#### Limitations

The type `CustomBaseClass` has to be a default-constructible class.
`basic_json` only supports copy/move construction/assignment if `CustomBaseClass` does so as well.

## Examples

??? example

The following code shows how to inject custom data and methods for each node.
```cpp
--8<-- "examples/json_base_class_t.cpp"
```

Output:

```json
--8<-- "examples/json_base_class_t.output"
```

## Version history

- Added in version 3.12.0.
1 change: 1 addition & 0 deletions docs/mkdocs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ nav:
- 'is_string': api/basic_json/is_string.md
- 'is_structured': api/basic_json/is_structured.md
- 'items': api/basic_json/items.md
- 'json_base_class_t': api/basic_json/json_base_class_t.md
- 'json_serializer': api/basic_json/json_serializer.md
- 'max_size': api/basic_json/max_size.md
- 'meta': api/basic_json/meta.md
Expand Down
31 changes: 31 additions & 0 deletions include/nlohmann/detail/json_custom_base_class.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once

#include <type_traits> // conditional, is_same

#include <nlohmann/detail/abi_macros.hpp>

NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{

/*!
@brief Default base class of the @ref basic_json class.
So that the correct implementations of the copy / move ctors / assign operators
of @ref basic_json do not require complex case distinctions
(no base class / custom base class used as customization point),
@ref basic_json always has a base class.
By default, this class is used because it is empty and thus has no effect
on the behavior of @ref basic_json.
*/
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
NLOHMANN_JSON_NAMESPACE_END
5 changes: 3 additions & 2 deletions include/nlohmann/detail/macro_scope.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
15 changes: 11 additions & 4 deletions include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -1200,11 +1204,12 @@ 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
other.assert_invariant(false);
other.assert_invariant(false); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved)

// invalidate payload
other.m_type = value_t::null;
Expand All @@ -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
Expand All @@ -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();
Expand Down
3 changes: 2 additions & 1 deletion include/nlohmann/json_fwd.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>, // cppcheck-suppress syntaxError
class CustomBaseClass = void>
class basic_json;

/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
Expand Down
Loading

0 comments on commit bed648c

Please sign in to comment.