Skip to content

Commit bed648c

Browse files
barcodenlohmannfalbrechtskirchingerbarcode
authored
Allow custom base class as node customization point (#3110)
Co-authored-by: Niels Lohmann <[email protected]> Co-authored-by: Florian Albrechtskirchinger <[email protected]> Co-authored-by: barcode <[email protected]>
1 parent f7973f4 commit bed648c

File tree

12 files changed

+595
-16
lines changed

12 files changed

+595
-16
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#include <iostream>
2+
#include <nlohmann/json.hpp>
3+
4+
class visitor_adaptor_with_metadata
5+
{
6+
public:
7+
template <class Fnc>
8+
void visit(const Fnc& fnc) const;
9+
10+
int metadata = 42;
11+
private:
12+
template <class Ptr, class Fnc>
13+
void do_visit(const Ptr& ptr, const Fnc& fnc) const;
14+
};
15+
16+
using json = nlohmann::basic_json <
17+
std::map,
18+
std::vector,
19+
std::string,
20+
bool,
21+
std::int64_t,
22+
std::uint64_t,
23+
double,
24+
std::allocator,
25+
nlohmann::adl_serializer,
26+
std::vector<std::uint8_t>,
27+
visitor_adaptor_with_metadata
28+
>;
29+
30+
template <class Fnc>
31+
void visitor_adaptor_with_metadata::visit(const Fnc& fnc) const
32+
{
33+
do_visit(json::json_pointer{}, fnc);
34+
}
35+
36+
template <class Ptr, class Fnc>
37+
void visitor_adaptor_with_metadata::do_visit(const Ptr& ptr, const Fnc& fnc) const
38+
{
39+
using value_t = nlohmann::detail::value_t;
40+
const json& j = *static_cast<const json*>(this);
41+
switch (j.type())
42+
{
43+
case value_t::object:
44+
fnc(ptr, j);
45+
for (const auto& entry : j.items())
46+
{
47+
entry.value().do_visit(ptr / entry.key(), fnc);
48+
}
49+
break;
50+
case value_t::array:
51+
fnc(ptr, j);
52+
for (std::size_t i = 0; i < j.size(); ++i)
53+
{
54+
j.at(i).do_visit(ptr / std::to_string(i), fnc);
55+
}
56+
break;
57+
case value_t::null:
58+
case value_t::string:
59+
case value_t::boolean:
60+
case value_t::number_integer:
61+
case value_t::number_unsigned:
62+
case value_t::number_float:
63+
case value_t::binary:
64+
fnc(ptr, j);
65+
break;
66+
case value_t::discarded:
67+
default:
68+
break;
69+
}
70+
}
71+
72+
int main()
73+
{
74+
// create a json object
75+
json j;
76+
j["null"];
77+
j["object"]["uint"] = 1U;
78+
j["object"].metadata = 21;
79+
80+
// visit and output
81+
j.visit(
82+
[&](const json::json_pointer & p,
83+
const json & j)
84+
{
85+
std::cout << (p.empty() ? std::string{"/"} : p.to_string())
86+
<< " - metadata = " << j.metadata << " -> " << j.dump() << '\n';
87+
});
88+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/ - metadata = 42 -> {"null":null,"object":{"uint":1}}
2+
/null - metadata = 42 -> null
3+
/object - metadata = 21 -> {"uint":1}
4+
/object/uint - metadata = 42 -> 1

docs/mkdocs/docs/api/basic_json/index.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ template<
1313
class NumberFloatType = double,
1414
template<typename U> class AllocatorType = std::allocator,
1515
template<typename T, typename SFINAE = void> class JSONSerializer = adl_serializer,
16-
class BinaryType = std::vector<std::uint8_t>
16+
class BinaryType = std::vector<std::uint8_t,
17+
class CustomBaseClass = void>
1718
>
1819
class basic_json;
1920
```
@@ -32,6 +33,7 @@ class basic_json;
3233
| `AllocatorType` | type of the allocator to use | |
3334
| `JSONSerializer` | the serializer to resolve internal calls to `to_json()` and `from_json()` | [`json_serializer`](json_serializer.md) |
3435
| `BinaryType` | type for binary arrays | [`binary_t`](binary_t.md) |
36+
| `CustomBaseClass` | extension point for user code | [`json_base_class_t`](json_base_class_t.md) |
3537

3638
## Specializations
3739

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# <small>nlohmann::basic_json::</small>json_base_class_t
2+
3+
```cpp
4+
using json_base_class_t = detail::json_base_class<CustomBaseClass>;
5+
```
6+
7+
The base class used to inject custom functionality into each instance of `basic_json`.
8+
Examples of such functionality might be metadata, additional member functions (e.g., visitors), or other application-specific code.
9+
10+
## Template parameters
11+
12+
`CustomBaseClass`
13+
: the base class to be added to `basic_json`
14+
15+
## Notes
16+
17+
#### Default type
18+
19+
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.
20+
21+
#### Limitations
22+
23+
The type `CustomBaseClass` has to be a default-constructible class.
24+
`basic_json` only supports copy/move construction/assignment if `CustomBaseClass` does so as well.
25+
26+
## Examples
27+
28+
??? example
29+
30+
The following code shows how to inject custom data and methods for each node.
31+
32+
```cpp
33+
--8<-- "examples/json_base_class_t.cpp"
34+
```
35+
36+
Output:
37+
38+
```json
39+
--8<-- "examples/json_base_class_t.output"
40+
```
41+
42+
## Version history
43+
44+
- Added in version 3.12.0.

docs/mkdocs/mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ nav:
141141
- 'is_string': api/basic_json/is_string.md
142142
- 'is_structured': api/basic_json/is_structured.md
143143
- 'items': api/basic_json/items.md
144+
- 'json_base_class_t': api/basic_json/json_base_class_t.md
144145
- 'json_serializer': api/basic_json/json_serializer.md
145146
- 'max_size': api/basic_json/max_size.md
146147
- 'meta': api/basic_json/meta.md
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#pragma once
2+
3+
#include <type_traits> // conditional, is_same
4+
5+
#include <nlohmann/detail/abi_macros.hpp>
6+
7+
NLOHMANN_JSON_NAMESPACE_BEGIN
8+
namespace detail
9+
{
10+
11+
/*!
12+
@brief Default base class of the @ref basic_json class.
13+
14+
So that the correct implementations of the copy / move ctors / assign operators
15+
of @ref basic_json do not require complex case distinctions
16+
(no base class / custom base class used as customization point),
17+
@ref basic_json always has a base class.
18+
By default, this class is used because it is empty and thus has no effect
19+
on the behavior of @ref basic_json.
20+
*/
21+
struct json_default_base {};
22+
23+
template<class T>
24+
using json_base_class = typename std::conditional <
25+
std::is_same<T, void>::value,
26+
json_default_base,
27+
T
28+
>::type;
29+
30+
} // namespace detail
31+
NLOHMANN_JSON_NAMESPACE_END

include/nlohmann/detail/macro_scope.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,12 +240,13 @@
240240
class NumberUnsignedType, class NumberFloatType, \
241241
template<typename> class AllocatorType, \
242242
template<typename, typename = void> class JSONSerializer, \
243-
class BinaryType>
243+
class BinaryType, \
244+
class CustomBaseClass>
244245

245246
#define NLOHMANN_BASIC_JSON_TPL \
246247
basic_json<ObjectType, ArrayType, StringType, BooleanType, \
247248
NumberIntegerType, NumberUnsignedType, NumberFloatType, \
248-
AllocatorType, JSONSerializer, BinaryType>
249+
AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>
249250

250251
// Macros to simplify conversion from/to types
251252

include/nlohmann/json.hpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
#include <nlohmann/detail/iterators/iteration_proxy.hpp>
4848
#include <nlohmann/detail/iterators/json_reverse_iterator.hpp>
4949
#include <nlohmann/detail/iterators/primitive_iterator.hpp>
50+
#include <nlohmann/detail/json_custom_base_class.hpp>
5051
#include <nlohmann/detail/json_pointer.hpp>
5152
#include <nlohmann/detail/json_ref.hpp>
5253
#include <nlohmann/detail/macro_scope.hpp>
@@ -93,6 +94,7 @@ The invariants are checked by member function assert_invariant().
9394
*/
9495
NLOHMANN_BASIC_JSON_TPL_DECLARATION
9596
class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions)
97+
: public ::nlohmann::detail::json_base_class<CustomBaseClass>
9698
{
9799
private:
98100
template<detail::value_t> friend struct detail::external_constructor;
@@ -119,6 +121,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
119121

120122
/// workaround type for MSVC
121123
using basic_json_t = NLOHMANN_BASIC_JSON_TPL;
124+
using json_base_class_t = ::nlohmann::detail::json_base_class<CustomBaseClass>;
122125

123126
JSON_PRIVATE_UNLESS_TESTED:
124127
// convenience aliases for types residing in namespace detail;
@@ -1132,7 +1135,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
11321135
/// @brief copy constructor
11331136
/// @sa https://json.nlohmann.me/api/basic_json/basic_json/
11341137
basic_json(const basic_json& other)
1135-
: m_type(other.m_type)
1138+
: json_base_class_t(other),
1139+
m_type(other.m_type)
11361140
{
11371141
// check of passed value is valid
11381142
other.assert_invariant();
@@ -1200,11 +1204,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
12001204
/// @brief move constructor
12011205
/// @sa https://json.nlohmann.me/api/basic_json/basic_json/
12021206
basic_json(basic_json&& other) noexcept
1203-
: m_type(std::move(other.m_type)),
1207+
: json_base_class_t(std::move(other)),
1208+
m_type(std::move(other.m_type)),
12041209
m_value(std::move(other.m_value))
12051210
{
12061211
// check that passed value is valid
1207-
other.assert_invariant(false);
1212+
other.assert_invariant(false); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved)
12081213

12091214
// invalidate payload
12101215
other.m_type = value_t::null;
@@ -1220,7 +1225,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
12201225
std::is_nothrow_move_constructible<value_t>::value&&
12211226
std::is_nothrow_move_assignable<value_t>::value&&
12221227
std::is_nothrow_move_constructible<json_value>::value&&
1223-
std::is_nothrow_move_assignable<json_value>::value
1228+
std::is_nothrow_move_assignable<json_value>::value&&
1229+
std::is_nothrow_move_assignable<json_base_class_t>::value
12241230
)
12251231
{
12261232
// check that passed value is valid
@@ -1229,6 +1235,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
12291235
using std::swap;
12301236
swap(m_type, other.m_type);
12311237
swap(m_value, other.m_value);
1238+
json_base_class_t::operator=(std::move(other));
12321239

12331240
set_parents();
12341241
assert_invariant();

include/nlohmann/json_fwd.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ template<template<typename U, typename V, typename... Args> class ObjectType =
4646
template<typename U> class AllocatorType = std::allocator,
4747
template<typename T, typename SFINAE = void> class JSONSerializer =
4848
adl_serializer,
49-
class BinaryType = std::vector<std::uint8_t>>
49+
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
50+
class CustomBaseClass = void>
5051
class basic_json;
5152

5253
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document

0 commit comments

Comments
 (0)