Skip to content

Commit eb7376b

Browse files
authored
Merge pull request nlohmann#2225 from nlohmann/issue2175
Simplify conversion from/to custom types
2 parents 1b4ea8f + 470f7c0 commit eb7376b

File tree

5 files changed

+245
-0
lines changed

5 files changed

+245
-0
lines changed

README.md

+36
Original file line numberDiff line numberDiff line change
@@ -869,6 +869,42 @@ Some important things:
869869
* In function `from_json`, use function [`at()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a93403e803947b86f4da2d1fb3345cf2c.html#a93403e803947b86f4da2d1fb3345cf2c) to access the object values rather than `operator[]`. In case a key does not exist, `at` throws an exception that you can handle, whereas `operator[]` exhibits undefined behavior.
870870
* You do not need to add serializers or deserializers for STL types like `std::vector`: the library already implements these.
871871

872+
#### Simplify your life with macros
873+
874+
If you just want to serialize/deserialize some structs, the `to_json`/`from_json` functions can be a lot of boilerplate.
875+
876+
There are two macros to make your life easier as long as you (1) want to use a JSON object as serialization and (2) want to use the member variable names as object keys in that object:
877+
878+
- `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(name, member1, member2, ...)` is to be defined inside of the namespace of the class/struct to create code for.
879+
- `NLOHMANN_DEFINE_TYPE_INTRUSIVE(name, member1, member2, ...)` is to be defined inside of the class/struct to create code for. This macro can also access private members.
880+
881+
In both macros, the first parameter is the name of the class/struct, and all remaining parameters name the members.
882+
883+
##### Examples
884+
885+
The `to_json`/`from_json` functions for the `person` struct above can be created with:
886+
887+
```cpp
888+
namespace ns {
889+
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person, name, address, age);
890+
}
891+
```
892+
893+
Here is an example with private members, where `NLOHMANN_DEFINE_TYPE_INTRUSIVE` is needed:
894+
895+
```cpp
896+
namespace ns {
897+
class address {
898+
private:
899+
std::string street;
900+
int housenumber;
901+
int postcode;
902+
903+
public:
904+
NLOHMANN_DEFINE_TYPE_INTRUSIVE(address, street, housenumber, postcode);
905+
};
906+
}
907+
```
872908

873909
#### How do I convert third-party types?
874910

include/nlohmann/detail/macro_scope.hpp

+41
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,44 @@
124124
basic_json<ObjectType, ArrayType, StringType, BooleanType, \
125125
NumberIntegerType, NumberUnsignedType, NumberFloatType, \
126126
AllocatorType, JSONSerializer, BinaryType>
127+
128+
// Macros to simplify conversion from/to types
129+
130+
#define NLOHMANN_JSON_EXPAND( x ) x
131+
#define NLOHMANN_JSON_GET_MACRO(_1,_2,_3,_4,_5,_6, _7, _8, _9, _10, _11, NAME,...) NAME
132+
133+
#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, NLOHMANN_JSON_PASTE11, \
134+
NLOHMANN_JSON_PASTE10, NLOHMANN_JSON_PASTE9, NLOHMANN_JSON_PASTE8, NLOHMANN_JSON_PASTE7, \
135+
NLOHMANN_JSON_PASTE6, NLOHMANN_JSON_PASTE5, NLOHMANN_JSON_PASTE4, NLOHMANN_JSON_PASTE3, \
136+
NLOHMANN_JSON_PASTE2, NLOHMANN_JSON_PASTE1)(__VA_ARGS__))
137+
#define NLOHMANN_JSON_PASTE2(func, v1) func(v1)
138+
#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2)
139+
#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3)
140+
#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4)
141+
#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5)
142+
#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6)
143+
#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7)
144+
#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8)
145+
#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8, v9)
146+
#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8, v9, v10)
147+
148+
#define NLOHMANN_JSON_TO(v1) j[#v1] = t.v1;
149+
#define NLOHMANN_JSON_FROM(v1) j.at(#v1).get_to(t.v1);
150+
151+
/*!
152+
@brief macro
153+
@def NLOHMANN_DEFINE_TYPE_INTRUSIVE
154+
@since version 3.9.0
155+
*/
156+
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \
157+
friend void to_json(nlohmann::json& j, const Type& t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
158+
friend void from_json(const nlohmann::json& j, Type& t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }
159+
160+
/*!
161+
@brief macro
162+
@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE
163+
@since version 3.9.0
164+
*/
165+
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \
166+
void to_json(nlohmann::json& j, const Type& t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
167+
void from_json(const nlohmann::json& j, Type& t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }

single_include/nlohmann/json.hpp

+41
Original file line numberDiff line numberDiff line change
@@ -2153,6 +2153,47 @@ JSON_HEDLEY_DIAGNOSTIC_POP
21532153
NumberIntegerType, NumberUnsignedType, NumberFloatType, \
21542154
AllocatorType, JSONSerializer, BinaryType>
21552155

2156+
// Macros to simplify conversion from/to types
2157+
2158+
#define NLOHMANN_JSON_EXPAND( x ) x
2159+
#define NLOHMANN_JSON_GET_MACRO(_1,_2,_3,_4,_5,_6, _7, _8, _9, _10, _11, NAME,...) NAME
2160+
2161+
#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, NLOHMANN_JSON_PASTE11, \
2162+
NLOHMANN_JSON_PASTE10, NLOHMANN_JSON_PASTE9, NLOHMANN_JSON_PASTE8, NLOHMANN_JSON_PASTE7, \
2163+
NLOHMANN_JSON_PASTE6, NLOHMANN_JSON_PASTE5, NLOHMANN_JSON_PASTE4, NLOHMANN_JSON_PASTE3, \
2164+
NLOHMANN_JSON_PASTE2, NLOHMANN_JSON_PASTE1)(__VA_ARGS__))
2165+
#define NLOHMANN_JSON_PASTE2(func, v1) func(v1)
2166+
#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2)
2167+
#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3)
2168+
#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4)
2169+
#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5)
2170+
#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6)
2171+
#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7)
2172+
#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8)
2173+
#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8, v9)
2174+
#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8, v9, v10)
2175+
2176+
#define NLOHMANN_JSON_TO(v1) j[#v1] = t.v1;
2177+
#define NLOHMANN_JSON_FROM(v1) j.at(#v1).get_to(t.v1);
2178+
2179+
/*!
2180+
@brief macro
2181+
@def NLOHMANN_DEFINE_TYPE_INTRUSIVE
2182+
@since version 3.9.0
2183+
*/
2184+
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \
2185+
friend void to_json(nlohmann::json& j, const Type& t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
2186+
friend void from_json(const nlohmann::json& j, Type& t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }
2187+
2188+
/*!
2189+
@brief macro
2190+
@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE
2191+
@since version 3.9.0
2192+
*/
2193+
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \
2194+
void to_json(nlohmann::json& j, const Type& t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
2195+
void from_json(const nlohmann::json& j, Type& t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }
2196+
21562197

21572198
namespace nlohmann
21582199
{

test/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ set(files
132132
src/unit-to_chars.cpp
133133
src/unit-ubjson.cpp
134134
src/unit-udt.cpp
135+
src/unit-udt_macro.cpp
135136
src/unit-unicode.cpp
136137
src/unit-user_defined_input.cpp
137138
src/unit-wstring.cpp)

test/src/unit-udt_macro.cpp

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
__ _____ _____ _____
3+
__| | __| | | | JSON for Modern C++ (test suite)
4+
| | |__ | | | | | | version 3.8.0
5+
|_____|_____|_____|_|___| https://github.com/nlohmann/json
6+
7+
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
8+
SPDX-License-Identifier: MIT
9+
Copyright (c) 2013-2019 Niels Lohmann <http://nlohmann.me>.
10+
11+
Permission is hereby granted, free of charge, to any person obtaining a copy
12+
of this software and associated documentation files (the "Software"), to deal
13+
in the Software without restriction, including without limitation the rights
14+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15+
copies of the Software, and to permit persons to whom the Software is
16+
furnished to do so, subject to the following conditions:
17+
18+
The above copyright notice and this permission notice shall be included in all
19+
copies or substantial portions of the Software.
20+
21+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27+
SOFTWARE.
28+
*/
29+
30+
#include "doctest_compatibility.h"
31+
32+
#include <nlohmann/json.hpp>
33+
using nlohmann::json;
34+
35+
#include <utility>
36+
37+
namespace persons
38+
{
39+
class person_with_private_data
40+
{
41+
private:
42+
std::string name;
43+
int age = 0;
44+
45+
public:
46+
bool operator==(const person_with_private_data& rhs) const
47+
{
48+
return std::tie(name, age) == std::tie(rhs.name, rhs.age);
49+
}
50+
51+
person_with_private_data() = default;
52+
person_with_private_data(std::string name, int age)
53+
: name(std::move(name))
54+
, age(age)
55+
{}
56+
57+
NLOHMANN_DEFINE_TYPE_INTRUSIVE(person_with_private_data, age, name);
58+
};
59+
60+
class person_without_private_data_1
61+
{
62+
public:
63+
std::string name;
64+
int age = 0;
65+
66+
bool operator==(const person_without_private_data_1& rhs) const
67+
{
68+
return std::tie(name, age) == std::tie(rhs.name, rhs.age);
69+
}
70+
71+
person_without_private_data_1() = default;
72+
person_without_private_data_1(std::string name, int age)
73+
: name(std::move(name))
74+
, age(age)
75+
{}
76+
77+
NLOHMANN_DEFINE_TYPE_INTRUSIVE(person_without_private_data_1, age, name);
78+
};
79+
80+
class person_without_private_data_2
81+
{
82+
public:
83+
std::string name;
84+
int age = 0;
85+
86+
bool operator==(const person_without_private_data_2& rhs) const
87+
{
88+
return std::tie(name, age) == std::tie(rhs.name, rhs.age);
89+
}
90+
91+
person_without_private_data_2() = default;
92+
person_without_private_data_2(std::string name, int age)
93+
: name(std::move(name))
94+
, age(age)
95+
{}
96+
};
97+
98+
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person_without_private_data_2, age, name);
99+
} // namespace persons
100+
101+
TEST_CASE_TEMPLATE("Serialization/deserialization via NLOHMANN_DEFINE_TYPE_INTRUSIVE", T,
102+
persons::person_with_private_data,
103+
persons::person_without_private_data_1,
104+
persons::person_without_private_data_2)
105+
{
106+
SECTION("person")
107+
{
108+
// serialization
109+
T p1("Erik", 1);
110+
CHECK(json(p1).dump() == "{\"age\":1,\"name\":\"Erik\"}");
111+
112+
// deserialization
113+
T p2 = json(p1);
114+
CHECK(p2 == p1);
115+
116+
// roundtrip
117+
CHECK(T(json(p1)) == p1);
118+
CHECK(json(T(json(p1))) == json(p1));
119+
120+
// check exception in case of missing field
121+
json j = json(p1);
122+
j.erase("age");
123+
T p3;
124+
CHECK_THROWS_WITH_AS(p3 = json(j), "[json.exception.out_of_range.403] key 'age' not found", json::out_of_range);
125+
}
126+
}

0 commit comments

Comments
 (0)