1 // __ _____ _____ _____
2 // __| | __| | | | JSON for Modern C++ (supporting code)
3 // | | |__ | | | | | | version 3.11.3
4 // |_____|_____|_____|_|___| https://github.com/nlohmann/json
5 //
6 // Copyright (c) 2013-2019 Niels Lohmann <http://nlohmann.me>.
7 // SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
8 // SPDX-License-Identifier: MIT
9
10 #include <set>
11 #include <sstream>
12 #include <string>
13
14 #include "doctest_compatibility.h"
15
16 #include <nlohmann/json.hpp>
17
18 // Test extending nlohmann::json by using a custom base class.
19 // Add some metadata to each node and test the behaviour of copy / move
20 template<class MetaDataType>
21 class json_metadata
22 {
23 public:
24 using metadata_t = MetaDataType;
metadata()25 metadata_t& metadata()
26 {
27 return m_metadata;
28 }
metadata() const29 const metadata_t& metadata() const
30 {
31 return m_metadata;
32 }
33 private:
34 metadata_t m_metadata = {};
35 };
36
37 template<class T>
38 using json_with_metadata =
39 nlohmann::basic_json <
40 std::map,
41 std::vector,
42 std::string,
43 bool,
44 std::int64_t,
45 std::uint64_t,
46 double,
47 std::allocator,
48 nlohmann::adl_serializer,
49 std::vector<std::uint8_t>,
50 json_metadata<T>
51 >;
52
53 TEST_CASE("JSON Node Metadata")
54 {
55 SECTION("type int")
56 {
57 using json = json_with_metadata<int>;
58 json null;
59 auto obj = json::object();
60 auto array = json::array();
61
62 null.metadata() = 1;
63 obj.metadata() = 2;
64 array.metadata() = 3;
65 auto copy = array;
66
67 CHECK(null.metadata() == 1);
68 CHECK(obj.metadata() == 2);
69 CHECK(array.metadata() == 3);
70 CHECK(copy.metadata() == 3);
71 }
72 SECTION("type vector<int>")
73 {
74 using json = json_with_metadata<std::vector<int>>;
75 json value;
76 value.metadata().emplace_back(1);
77 auto copy = value;
78 value.metadata().emplace_back(2);
79
80 CHECK(copy.metadata().size() == 1);
81 CHECK(copy.metadata().at(0) == 1);
82 CHECK(value.metadata().size() == 2);
83 CHECK(value.metadata().at(0) == 1);
84 CHECK(value.metadata().at(1) == 2);
85 }
86 SECTION("copy ctor")
87 {
88 using json = json_with_metadata<std::vector<int>>;
89 json value;
90 value.metadata().emplace_back(1);
91 value.metadata().emplace_back(2);
92
93 json copy = value;
94
95 CHECK(copy.metadata().size() == 2);
96 CHECK(copy.metadata().at(0) == 1);
97 CHECK(copy.metadata().at(1) == 2);
98 CHECK(value.metadata().size() == 2);
99 CHECK(value.metadata().at(0) == 1);
100 CHECK(value.metadata().at(1) == 2);
101
102 value.metadata().clear();
103 CHECK(copy.metadata().size() == 2);
104 CHECK(value.metadata().size() == 0);
105 }
106 SECTION("move ctor")
107 {
108 using json = json_with_metadata<std::vector<int>>;
109 json value;
110 value.metadata().emplace_back(1);
111 value.metadata().emplace_back(2);
112
113 const json moved = std::move(value);
114
115 CHECK(moved.metadata().size() == 2);
116 CHECK(moved.metadata().at(0) == 1);
117 CHECK(moved.metadata().at(1) == 2);
118 }
119 SECTION("move assign")
120 {
121 using json = json_with_metadata<std::vector<int>>;
122 json value;
123 value.metadata().emplace_back(1);
124 value.metadata().emplace_back(2);
125
126 json moved;
127 moved = std::move(value);
128
129 CHECK(moved.metadata().size() == 2);
130 CHECK(moved.metadata().at(0) == 1);
131 CHECK(moved.metadata().at(1) == 2);
132 }
133 SECTION("copy assign")
134 {
135 using json = json_with_metadata<std::vector<int>>;
136 json value;
137 value.metadata().emplace_back(1);
138 value.metadata().emplace_back(2);
139
140 json copy;
141 copy = value;
142
143 CHECK(copy.metadata().size() == 2);
144 CHECK(copy.metadata().at(0) == 1);
145 CHECK(copy.metadata().at(1) == 2);
146 CHECK(value.metadata().size() == 2);
147 CHECK(value.metadata().at(0) == 1);
148 CHECK(value.metadata().at(1) == 2);
149
150 value.metadata().clear();
151 CHECK(copy.metadata().size() == 2);
152 CHECK(value.metadata().size() == 0);
153 }
154 SECTION("type unique_ptr<int>")
155 {
156 using json = json_with_metadata<std::unique_ptr<int>>;
157 json value;
158 value.metadata().reset(new int(42)); // NOLINT(cppcoreguidelines-owning-memory)
159 auto moved = std::move(value);
160
161 CHECK(moved.metadata() != nullptr);
162 CHECK(*moved.metadata() == 42);
163 }
164 SECTION("type vector<int> in json array")
165 {
166 using json = json_with_metadata<std::vector<int>>;
167 json value;
168 value.metadata().emplace_back(1);
169 value.metadata().emplace_back(2);
170
171 json const array(10, value);
172
173 CHECK(value.metadata().size() == 2);
174 CHECK(value.metadata().at(0) == 1);
175 CHECK(value.metadata().at(1) == 2);
176
177 for (const auto& val : array)
178 {
179 CHECK(val.metadata().size() == 2);
180 CHECK(val.metadata().at(0) == 1);
181 CHECK(val.metadata().at(1) == 2);
182 }
183 }
184 }
185
186 // Test extending nlohmann::json by using a custom base class.
187 // Add a custom member function template iterating over the whole json tree.
188 class visitor_adaptor
189 {
190 public:
191 template <class Fnc>
192 void visit(const Fnc& fnc) const;
193 private:
194 template <class Ptr, class Fnc>
195 void do_visit(const Ptr& ptr, const Fnc& fnc) const;
196 };
197
198 using json_with_visitor_t = nlohmann::basic_json <
199 std::map,
200 std::vector,
201 std::string,
202 bool,
203 std::int64_t,
204 std::uint64_t,
205 double,
206 std::allocator,
207 nlohmann::adl_serializer,
208 std::vector<std::uint8_t>,
209 visitor_adaptor
210 >;
211
212 template <class Fnc>
visit(const Fnc & fnc) const213 void visitor_adaptor::visit(const Fnc& fnc) const
214 {
215 do_visit(json_with_visitor_t::json_pointer{}, fnc);
216 }
217
218 template <class Ptr, class Fnc>
do_visit(const Ptr & ptr,const Fnc & fnc) const219 void visitor_adaptor::do_visit(const Ptr& ptr, const Fnc& fnc) const
220 {
221 using value_t = nlohmann::detail::value_t;
222 const json_with_visitor_t& json = *static_cast<const json_with_visitor_t*>(this); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
223 switch (json.type())
224 {
225 case value_t::object:
226 for (const auto& entry : json.items())
227 {
228 entry.value().do_visit(ptr / entry.key(), fnc);
229 }
230 break;
231 case value_t::array:
232 for (std::size_t i = 0; i < json.size(); ++i)
233 {
234 json.at(i).do_visit(ptr / std::to_string(i), fnc);
235 }
236 break;
237 case value_t::discarded:
238 break;
239 case value_t::null:
240 case value_t::string:
241 case value_t::boolean:
242 case value_t::number_integer:
243 case value_t::number_unsigned:
244 case value_t::number_float:
245 case value_t::binary:
246 default:
247 fnc(ptr, json);
248 }
249 }
250
251 TEST_CASE("JSON Visit Node")
252 {
253 json_with_visitor_t json;
254 json["null"];
255 json["int"] = -1;
256 json["uint"] = 1U;
257 json["float"] = 1.0;
258 json["boolean"] = true;
259 json["string"] = "string";
260 json["array"].push_back(0);
261 json["array"].push_back(1);
262 json["array"].push_back(json);
263
264 std::set<std::string> expected
265 {
266 "/null - null - null",
267 "/int - number_integer - -1",
268 "/uint - number_unsigned - 1",
269 "/float - number_float - 1.0",
270 "/boolean - boolean - true",
271 "/string - string - \"string\"",
272 "/array/0 - number_integer - 0",
273 "/array/1 - number_integer - 1",
274
275 "/array/2/null - null - null",
276 "/array/2/int - number_integer - -1",
277 "/array/2/uint - number_unsigned - 1",
278 "/array/2/float - number_float - 1.0",
279 "/array/2/boolean - boolean - true",
280 "/array/2/string - string - \"string\"",
281 "/array/2/array/0 - number_integer - 0",
282 "/array/2/array/1 - number_integer - 1"
283 };
284
285 json.visit(
286 [&](const json_with_visitor_t::json_pointer & p,
287 const json_with_visitor_t& j)
__anonf2bb344a0102(const json_with_visitor_t::json_pointer & p, const json_with_visitor_t& j) 288 {
289 std::stringstream str;
290 str << p.to_string() << " - " ;
291 using value_t = nlohmann::detail::value_t;
292 switch (j.type())
293 {
294 case value_t::object:
295 str << "object";
296 break;
297 case value_t::array:
298 str << "array";
299 break;
300 case value_t::discarded:
301 str << "discarded";
302 break;
303 case value_t::null:
304 str << "null";
305 break;
306 case value_t::string:
307 str << "string";
308 break;
309 case value_t::boolean:
310 str << "boolean";
311 break;
312 case value_t::number_integer:
313 str << "number_integer";
314 break;
315 case value_t::number_unsigned:
316 str << "number_unsigned";
317 break;
318 case value_t::number_float:
319 str << "number_float";
320 break;
321 case value_t::binary:
322 str << "binary";
323 break;
324 default:
325 str << "error";
326 break;
327 }
328 str << " - " << j.dump();
329 CHECK(json.at(p) == j);
330 INFO(str.str());
331 CHECK(expected.count(str.str()) == 1);
332 expected.erase(str.str());
333 }
334 );
335 CHECK(expected.empty());
336 }
337