• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //     __ _____ _____ _____
2 //  __|  |   __|     |   | |  JSON for Modern C++ (supporting code)
3 // |  |  |__   |  |  | | | |  version 3.11.2
4 // |_____|_____|_____|_|___|  https://github.com/nlohmann/json
5 //
6 // SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>
7 // SPDX-License-Identifier: MIT
8 
9 #include "doctest_compatibility.h"
10 
11 #define JSON_TESTS_PRIVATE
12 #include <nlohmann/json.hpp>
13 using nlohmann::json;
14 #ifdef JSON_TEST_NO_GLOBAL_UDLS
15     using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
16 #endif
17 
18 #include <map>
19 #include <sstream>
20 
21 TEST_CASE("JSON pointers")
22 {
23     SECTION("errors")
24     {
25         CHECK_THROWS_WITH_AS(json::json_pointer("foo"),
26                              "[json.exception.parse_error.107] parse error at byte 1: JSON pointer must be empty or begin with '/' - was: 'foo'", json::parse_error&);
27 
28         CHECK_THROWS_WITH_AS(json::json_pointer("/~~"),
29                              "[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&);
30 
31         CHECK_THROWS_WITH_AS(json::json_pointer("/~"),
32                              "[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&);
33 
34         json::json_pointer p;
35         CHECK_THROWS_WITH_AS(p.top(),
36                              "[json.exception.out_of_range.405] JSON pointer has no parent", json::out_of_range&);
37         CHECK_THROWS_WITH_AS(p.pop_back(),
38                              "[json.exception.out_of_range.405] JSON pointer has no parent", json::out_of_range&);
39 
40         SECTION("array index error")
41         {
42             json v = {1, 2, 3, 4};
43             json::json_pointer ptr("/10e");
44             CHECK_THROWS_WITH_AS(v[ptr],
45                                  "[json.exception.out_of_range.404] unresolved reference token '10e'", json::out_of_range&);
46         }
47     }
48 
49     SECTION("examples from RFC 6901")
50     {
51         SECTION("nonconst access")
52         {
53             json j = R"(
54             {
55                 "foo": ["bar", "baz"],
56                 "": 0,
57                 "a/b": 1,
58                 "c%d": 2,
59                 "e^f": 3,
60                 "g|h": 4,
61                 "i\\j": 5,
62                 "k\"l": 6,
63                 " ": 7,
64                 "m~n": 8
65             }
66             )"_json;
67 
68             // the whole document
69             CHECK(j[json::json_pointer()] == j);
70             CHECK(j[json::json_pointer("")] == j);
71             CHECK(j.contains(json::json_pointer()));
72             CHECK(j.contains(json::json_pointer("")));
73 
74             // array access
75             CHECK(j[json::json_pointer("/foo")] == j["foo"]);
76             CHECK(j.contains(json::json_pointer("/foo")));
77             CHECK(j[json::json_pointer("/foo/0")] == j["foo"][0]);
78             CHECK(j[json::json_pointer("/foo/1")] == j["foo"][1]);
79             CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
80             CHECK(j.contains(json::json_pointer("/foo/0")));
81             CHECK(j.contains(json::json_pointer("/foo/1")));
82             CHECK(!j.contains(json::json_pointer("/foo/3")));
83             CHECK(!j.contains(json::json_pointer("/foo/+")));
84             CHECK(!j.contains(json::json_pointer("/foo/1+2")));
85             CHECK(!j.contains(json::json_pointer("/foo/-")));
86 
87             // checked array access
88             CHECK(j.at(json::json_pointer("/foo/0")) == j["foo"][0]);
89             CHECK(j.at(json::json_pointer("/foo/1")) == j["foo"][1]);
90 
91             // empty string access
92             CHECK(j[json::json_pointer("/")] == j[""]);
93             CHECK(j.contains(json::json_pointer("")));
94             CHECK(j.contains(json::json_pointer("/")));
95 
96             // other cases
97             CHECK(j[json::json_pointer("/ ")] == j[" "]);
98             CHECK(j[json::json_pointer("/c%d")] == j["c%d"]);
99             CHECK(j[json::json_pointer("/e^f")] == j["e^f"]);
100             CHECK(j[json::json_pointer("/g|h")] == j["g|h"]);
101             CHECK(j[json::json_pointer("/i\\j")] == j["i\\j"]);
102             CHECK(j[json::json_pointer("/k\"l")] == j["k\"l"]);
103 
104             // contains
105             CHECK(j.contains(json::json_pointer("/ ")));
106             CHECK(j.contains(json::json_pointer("/c%d")));
107             CHECK(j.contains(json::json_pointer("/e^f")));
108             CHECK(j.contains(json::json_pointer("/g|h")));
109             CHECK(j.contains(json::json_pointer("/i\\j")));
110             CHECK(j.contains(json::json_pointer("/k\"l")));
111 
112             // checked access
113             CHECK(j.at(json::json_pointer("/ ")) == j[" "]);
114             CHECK(j.at(json::json_pointer("/c%d")) == j["c%d"]);
115             CHECK(j.at(json::json_pointer("/e^f")) == j["e^f"]);
116             CHECK(j.at(json::json_pointer("/g|h")) == j["g|h"]);
117             CHECK(j.at(json::json_pointer("/i\\j")) == j["i\\j"]);
118             CHECK(j.at(json::json_pointer("/k\"l")) == j["k\"l"]);
119 
120             // escaped access
121             CHECK(j[json::json_pointer("/a~1b")] == j["a/b"]);
122             CHECK(j[json::json_pointer("/m~0n")] == j["m~n"]);
123             CHECK(j.contains(json::json_pointer("/a~1b")));
124             CHECK(j.contains(json::json_pointer("/m~0n")));
125 
126             // unescaped access
127             // access to nonexisting values yield object creation
128             CHECK(!j.contains(json::json_pointer("/a/b")));
129             CHECK_NOTHROW(j[json::json_pointer("/a/b")] = 42);
130             CHECK(j.contains(json::json_pointer("/a/b")));
131             CHECK(j["a"]["b"] == json(42));
132 
133             CHECK(!j.contains(json::json_pointer("/a/c/1")));
134             CHECK_NOTHROW(j[json::json_pointer("/a/c/1")] = 42);
135             CHECK(j["a"]["c"] == json({nullptr, 42}));
136             CHECK(j.contains(json::json_pointer("/a/c/1")));
137 
138             CHECK(!j.contains(json::json_pointer("/a/d/-")));
139             CHECK_NOTHROW(j[json::json_pointer("/a/d/-")] = 42);
140             CHECK(!j.contains(json::json_pointer("/a/d/-")));
141             CHECK(j["a"]["d"] == json::array({42}));
142             // "/a/b" works for JSON {"a": {"b": 42}}
143             CHECK(json({{"a", {{"b", 42}}}})[json::json_pointer("/a/b")] == json(42));
144 
145             // unresolved access
146             json j_primitive = 1;
147             CHECK_THROWS_WITH_AS(j_primitive["/foo"_json_pointer],
148                                  "[json.exception.out_of_range.404] unresolved reference token 'foo'", json::out_of_range&);
149             CHECK_THROWS_WITH_AS(j_primitive.at("/foo"_json_pointer),
150                                  "[json.exception.out_of_range.404] unresolved reference token 'foo'", json::out_of_range&);
151             CHECK(!j_primitive.contains(json::json_pointer("/foo")));
152         }
153 
154         SECTION("const access")
155         {
156             const json j = R"(
157             {
158                 "foo": ["bar", "baz"],
159                 "": 0,
160                 "a/b": 1,
161                 "c%d": 2,
162                 "e^f": 3,
163                 "g|h": 4,
164                 "i\\j": 5,
165                 "k\"l": 6,
166                 " ": 7,
167                 "m~n": 8
168             }
169             )"_json;
170 
171             // the whole document
172             CHECK(j[json::json_pointer()] == j);
173             CHECK(j[json::json_pointer("")] == j);
174 
175             // array access
176             CHECK(j[json::json_pointer("/foo")] == j["foo"]);
177             CHECK(j[json::json_pointer("/foo/0")] == j["foo"][0]);
178             CHECK(j[json::json_pointer("/foo/1")] == j["foo"][1]);
179             CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
180 
181             // checked array access
182             CHECK(j.at(json::json_pointer("/foo/0")) == j["foo"][0]);
183             CHECK(j.at(json::json_pointer("/foo/1")) == j["foo"][1]);
184 
185             // empty string access
186             CHECK(j[json::json_pointer("/")] == j[""]);
187 
188             // other cases
189             CHECK(j[json::json_pointer("/ ")] == j[" "]);
190             CHECK(j[json::json_pointer("/c%d")] == j["c%d"]);
191             CHECK(j[json::json_pointer("/e^f")] == j["e^f"]);
192             CHECK(j[json::json_pointer("/g|h")] == j["g|h"]);
193             CHECK(j[json::json_pointer("/i\\j")] == j["i\\j"]);
194             CHECK(j[json::json_pointer("/k\"l")] == j["k\"l"]);
195 
196             // checked access
197             CHECK(j.at(json::json_pointer("/ ")) == j[" "]);
198             CHECK(j.at(json::json_pointer("/c%d")) == j["c%d"]);
199             CHECK(j.at(json::json_pointer("/e^f")) == j["e^f"]);
200             CHECK(j.at(json::json_pointer("/g|h")) == j["g|h"]);
201             CHECK(j.at(json::json_pointer("/i\\j")) == j["i\\j"]);
202             CHECK(j.at(json::json_pointer("/k\"l")) == j["k\"l"]);
203 
204             // escaped access
205             CHECK(j[json::json_pointer("/a~1b")] == j["a/b"]);
206             CHECK(j[json::json_pointer("/m~0n")] == j["m~n"]);
207 
208             // unescaped access
209             CHECK_THROWS_WITH_AS(j.at(json::json_pointer("/a/b")),
210                                  "[json.exception.out_of_range.403] key 'a' not found", json::out_of_range&);
211 
212             // unresolved access
213             const json j_primitive = 1;
214             CHECK_THROWS_WITH_AS(j_primitive["/foo"_json_pointer],
215                                  "[json.exception.out_of_range.404] unresolved reference token 'foo'", json::out_of_range&);
216             CHECK_THROWS_WITH_AS(j_primitive.at("/foo"_json_pointer),
217                                  "[json.exception.out_of_range.404] unresolved reference token 'foo'", json::out_of_range&);
218         }
219 
220         SECTION("user-defined string literal")
221         {
222             json j = R"(
223             {
224                 "foo": ["bar", "baz"],
225                 "": 0,
226                 "a/b": 1,
227                 "c%d": 2,
228                 "e^f": 3,
229                 "g|h": 4,
230                 "i\\j": 5,
231                 "k\"l": 6,
232                 " ": 7,
233                 "m~n": 8
234             }
235             )"_json;
236 
237             // the whole document
238             CHECK(j[""_json_pointer] == j);
239             CHECK(j.contains(""_json_pointer));
240 
241             // array access
242             CHECK(j["/foo"_json_pointer] == j["foo"]);
243             CHECK(j["/foo/0"_json_pointer] == j["foo"][0]);
244             CHECK(j["/foo/1"_json_pointer] == j["foo"][1]);
245             CHECK(j.contains("/foo"_json_pointer));
246             CHECK(j.contains("/foo/0"_json_pointer));
247             CHECK(j.contains("/foo/1"_json_pointer));
248             CHECK(!j.contains("/foo/-"_json_pointer));
249         }
250     }
251 
252     SECTION("array access")
253     {
254         SECTION("nonconst access")
255         {
256             json j = {1, 2, 3};
257             const json j_const = j;
258 
259             // check reading access
260             CHECK(j["/0"_json_pointer] == j[0]);
261             CHECK(j["/1"_json_pointer] == j[1]);
262             CHECK(j["/2"_json_pointer] == j[2]);
263 
264             // assign to existing index
265             j["/1"_json_pointer] = 13;
266             CHECK(j[1] == json(13));
267 
268             // assign to nonexisting index
269             j["/3"_json_pointer] = 33;
270             CHECK(j[3] == json(33));
271 
272             // assign to nonexisting index (with gap)
273             j["/5"_json_pointer] = 55;
274             CHECK(j == json({1, 13, 3, 33, nullptr, 55}));
275 
276             // error with leading 0
277             CHECK_THROWS_WITH_AS(j["/01"_json_pointer],
278                                  "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&);
279             CHECK_THROWS_WITH_AS(j_const["/01"_json_pointer],
280                                  "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&);
281             CHECK_THROWS_WITH_AS(j.at("/01"_json_pointer),
282                                  "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&);
283             CHECK_THROWS_WITH_AS(j_const.at("/01"_json_pointer),
284                                  "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&);
285 
286             CHECK(!j.contains("/01"_json_pointer));
287             CHECK(!j.contains("/01"_json_pointer));
288             CHECK(!j_const.contains("/01"_json_pointer));
289             CHECK(!j_const.contains("/01"_json_pointer));
290 
291             // error with incorrect numbers
292             CHECK_THROWS_WITH_AS(j["/one"_json_pointer] = 1,
293                                  "[json.exception.parse_error.109] parse error: array index 'one' is not a number", json::parse_error&);
294             CHECK_THROWS_WITH_AS(j_const["/one"_json_pointer] == 1,
295                                  "[json.exception.parse_error.109] parse error: array index 'one' is not a number", json::parse_error&);
296 
297             CHECK_THROWS_WITH_AS(j.at("/one"_json_pointer) = 1,
298                                  "[json.exception.parse_error.109] parse error: array index 'one' is not a number", json::parse_error&);
299             CHECK_THROWS_WITH_AS(j_const.at("/one"_json_pointer) == 1,
300                                  "[json.exception.parse_error.109] parse error: array index 'one' is not a number", json::parse_error&);
301 
302             CHECK_THROWS_WITH_AS(j["/+1"_json_pointer] = 1,
303                                  "[json.exception.parse_error.109] parse error: array index '+1' is not a number", json::parse_error&);
304             CHECK_THROWS_WITH_AS(j_const["/+1"_json_pointer] == 1,
305                                  "[json.exception.parse_error.109] parse error: array index '+1' is not a number", json::parse_error&);
306 
307             CHECK_THROWS_WITH_AS(j["/1+1"_json_pointer] = 1,
308                                  "[json.exception.out_of_range.404] unresolved reference token '1+1'", json::out_of_range&);
309             CHECK_THROWS_WITH_AS(j_const["/1+1"_json_pointer] == 1,
310                                  "[json.exception.out_of_range.404] unresolved reference token '1+1'", json::out_of_range&);
311 
312             {
313                 auto too_large_index = std::to_string((std::numeric_limits<unsigned long long>::max)()) + "1";
314                 json::json_pointer jp(std::string("/") + too_large_index);
315                 std::string throw_msg = std::string("[json.exception.out_of_range.404] unresolved reference token '") + too_large_index + "'";
316 
317                 CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
318                 CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
319             }
320 
321             // on some machines, the check below is not constant
322             DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
323             DOCTEST_MSVC_SUPPRESS_WARNING(4127)
324 
325             if (sizeof(typename json::size_type) < sizeof(unsigned long long))
326             {
327                 auto size_type_max_uul = static_cast<unsigned long long>((std::numeric_limits<json::size_type>::max)());
328                 auto too_large_index = std::to_string(size_type_max_uul);
329                 json::json_pointer jp(std::string("/") + too_large_index);
330                 std::string throw_msg = std::string("[json.exception.out_of_range.410] array index ") + too_large_index + " exceeds size_type";
331 
332                 CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
333                 CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
334             }
335 
336             DOCTEST_MSVC_SUPPRESS_WARNING_POP
337 
338             CHECK_THROWS_WITH_AS(j.at("/one"_json_pointer) = 1,
339                                  "[json.exception.parse_error.109] parse error: array index 'one' is not a number", json::parse_error&);
340             CHECK_THROWS_WITH_AS(j_const.at("/one"_json_pointer) == 1,
341                                  "[json.exception.parse_error.109] parse error: array index 'one' is not a number", json::parse_error&);
342 
343             CHECK(!j.contains("/one"_json_pointer));
344             CHECK(!j.contains("/one"_json_pointer));
345             CHECK(!j_const.contains("/one"_json_pointer));
346             CHECK(!j_const.contains("/one"_json_pointer));
347 
348             CHECK_THROWS_WITH_AS(json({{"/list/0", 1}, {"/list/1", 2}, {"/list/three", 3}}).unflatten(),
349             "[json.exception.parse_error.109] parse error: array index 'three' is not a number", json::parse_error&);
350 
351             // assign to "-"
352             j["/-"_json_pointer] = 99;
353             CHECK(j == json({1, 13, 3, 33, nullptr, 55, 99}));
354 
355             // error when using "-" in const object
356             CHECK_THROWS_WITH_AS(j_const["/-"_json_pointer],
357                                  "[json.exception.out_of_range.402] array index '-' (3) is out of range", json::out_of_range&);
358             CHECK(!j_const.contains("/-"_json_pointer));
359 
360             // error when using "-" with at
361             CHECK_THROWS_WITH_AS(j.at("/-"_json_pointer),
362                                  "[json.exception.out_of_range.402] array index '-' (7) is out of range", json::out_of_range&);
363             CHECK_THROWS_WITH_AS(j_const.at("/-"_json_pointer),
364                                  "[json.exception.out_of_range.402] array index '-' (3) is out of range", json::out_of_range&);
365             CHECK(!j_const.contains("/-"_json_pointer));
366         }
367 
368         SECTION("const access")
369         {
370             const json j = {1, 2, 3};
371 
372             // check reading access
373             CHECK(j["/0"_json_pointer] == j[0]);
374             CHECK(j["/1"_json_pointer] == j[1]);
375             CHECK(j["/2"_json_pointer] == j[2]);
376 
377             // assign to nonexisting index
378             CHECK_THROWS_WITH_AS(j.at("/3"_json_pointer),
379                                  "[json.exception.out_of_range.401] array index 3 is out of range", json::out_of_range&);
380             CHECK(!j.contains("/3"_json_pointer));
381 
382             // assign to nonexisting index (with gap)
383             CHECK_THROWS_WITH_AS(j.at("/5"_json_pointer),
384                                  "[json.exception.out_of_range.401] array index 5 is out of range", json::out_of_range&);
385             CHECK(!j.contains("/5"_json_pointer));
386 
387             // assign to "-"
388             CHECK_THROWS_WITH_AS(j["/-"_json_pointer],
389                                  "[json.exception.out_of_range.402] array index '-' (3) is out of range", json::out_of_range&);
390             CHECK_THROWS_WITH_AS(j.at("/-"_json_pointer),
391                                  "[json.exception.out_of_range.402] array index '-' (3) is out of range", json::out_of_range&);
392             CHECK(!j.contains("/-"_json_pointer));
393         }
394     }
395 
396     SECTION("flatten")
397     {
398         json j =
399         {
400             {"pi", 3.141},
401             {"happy", true},
402             {"name", "Niels"},
403             {"nothing", nullptr},
404             {
405                 "answer", {
406                     {"everything", 42}
407                 }
408             },
409             {"list", {1, 0, 2}},
410             {
411                 "object", {
412                     {"currency", "USD"},
413                     {"value", 42.99},
414                     {"", "empty string"},
415                     {"/", "slash"},
416                     {"~", "tilde"},
417                     {"~1", "tilde1"}
418                 }
419             }
420         };
421 
422         json j_flatten =
423         {
424             {"/pi", 3.141},
425             {"/happy", true},
426             {"/name", "Niels"},
427             {"/nothing", nullptr},
428             {"/answer/everything", 42},
429             {"/list/0", 1},
430             {"/list/1", 0},
431             {"/list/2", 2},
432             {"/object/currency", "USD"},
433             {"/object/value", 42.99},
434             {"/object/", "empty string"},
435             {"/object/~1", "slash"},
436             {"/object/~0", "tilde"},
437             {"/object/~01", "tilde1"}
438         };
439 
440         // check if flattened result is as expected
441         CHECK(j.flatten() == j_flatten);
442 
443         // check if unflattened result is as expected
444         CHECK(j_flatten.unflatten() == j);
445 
446         // error for nonobjects
447         CHECK_THROWS_WITH_AS(json(1).unflatten(),
448                              "[json.exception.type_error.314] only objects can be unflattened", json::type_error&);
449 
450         // error for nonprimitve values
451 #if JSON_DIAGNOSTICS
452         CHECK_THROWS_WITH_AS(json({{"/1", {1, 2, 3}}}).unflatten(), "[json.exception.type_error.315] (/~11) values in object must be primitive", json::type_error&);
453 #else
454         CHECK_THROWS_WITH_AS(json({{"/1", {1, 2, 3}}}).unflatten(), "[json.exception.type_error.315] values in object must be primitive", json::type_error&);
455 #endif
456 
457         // error for conflicting values
458         json j_error = {{"", 42}, {"/foo", 17}};
459         CHECK_THROWS_WITH_AS(j_error.unflatten(),
460                              "[json.exception.type_error.313] invalid value to unflatten", json::type_error&);
461 
462         // explicit roundtrip check
463         CHECK(j.flatten().unflatten() == j);
464 
465         // roundtrip for primitive values
466         json j_null;
467         CHECK(j_null.flatten().unflatten() == j_null);
468         json j_number = 42;
469         CHECK(j_number.flatten().unflatten() == j_number);
470         json j_boolean = false;
471         CHECK(j_boolean.flatten().unflatten() == j_boolean);
472         json j_string = "foo";
473         CHECK(j_string.flatten().unflatten() == j_string);
474 
475         // roundtrip for empty structured values (will be unflattened to null)
476         json j_array(json::value_t::array);
477         CHECK(j_array.flatten().unflatten() == json());
478         json j_object(json::value_t::object);
479         CHECK(j_object.flatten().unflatten() == json());
480     }
481 
482     SECTION("string representation")
483     {
484         for (const auto* ptr_str :
485                 {"", "/foo", "/foo/0", "/", "/a~1b", "/c%d", "/e^f", "/g|h", "/i\\j", "/k\"l", "/ ", "/m~0n"
486                 })
487         {
488             json::json_pointer ptr(ptr_str);
489             std::stringstream ss;
490             ss << ptr;
491             CHECK(ptr.to_string() == ptr_str);
492             CHECK(std::string(ptr) == ptr_str);
493             CHECK(ss.str() == ptr_str);
494         }
495     }
496 
497     SECTION("conversion")
498     {
499         SECTION("array")
500         {
501             json j;
502             // all numbers -> array
503             j["/12"_json_pointer] = 0;
504             CHECK(j.is_array());
505         }
506 
507         SECTION("object")
508         {
509             json j;
510             // contains a number, but is not a number -> object
511             j["/a12"_json_pointer] = 0;
512             CHECK(j.is_object());
513         }
514     }
515 
516     SECTION("empty, push, pop and parent")
517     {
518         const json j =
519         {
520             {"", "Hello"},
521             {"pi", 3.141},
522             {"happy", true},
523             {"name", "Niels"},
524             {"nothing", nullptr},
525             {
526                 "answer", {
527                     {"everything", 42}
528                 }
529             },
530             {"list", {1, 0, 2}},
531             {
532                 "object", {
533                     {"currency", "USD"},
534                     {"value", 42.99},
535                     {"", "empty string"},
536                     {"/", "slash"},
537                     {"~", "tilde"},
538                     {"~1", "tilde1"}
539                 }
540             }
541         };
542 
543         // empty json_pointer returns the root JSON-object
544         auto ptr = ""_json_pointer;
545         CHECK(ptr.empty());
546         CHECK(j[ptr] == j);
547 
548         // simple field access
549         ptr.push_back("pi");
550         CHECK(!ptr.empty());
551         CHECK(j[ptr] == j["pi"]);
552 
553         ptr.pop_back();
554         CHECK(ptr.empty());
555         CHECK(j[ptr] == j);
556 
557         // object and children access
558         const std::string answer("answer");
559         ptr.push_back(answer);
560         ptr.push_back("everything");
561         CHECK(!ptr.empty());
562         CHECK(j[ptr] == j["answer"]["everything"]);
563 
564         // check access via const pointer
565         const auto cptr = ptr;
566         CHECK(cptr.back() == "everything");
567 
568         ptr.pop_back();
569         ptr.pop_back();
570         CHECK(ptr.empty());
571         CHECK(j[ptr] == j);
572 
573         // push key which has to be encoded
574         ptr.push_back("object");
575         ptr.push_back("/");
576         CHECK(j[ptr] == j["object"]["/"]);
577         CHECK(ptr.to_string() == "/object/~1");
578 
579         CHECK(j[ptr.parent_pointer()] == j["object"]);
580         ptr = ptr.parent_pointer().parent_pointer();
581         CHECK(ptr.empty());
582         CHECK(j[ptr] == j);
583         // parent-pointer of the empty json_pointer is empty
584         ptr = ptr.parent_pointer();
585         CHECK(ptr.empty());
586         CHECK(j[ptr] == j);
587 
588         CHECK_THROWS_WITH(ptr.pop_back(),
589                           "[json.exception.out_of_range.405] JSON pointer has no parent");
590     }
591 
592     SECTION("operators")
593     {
594         const json j =
595         {
596             {"", "Hello"},
597             {"pi", 3.141},
598             {"happy", true},
599             {"name", "Niels"},
600             {"nothing", nullptr},
601             {
602                 "answer", {
603                     {"everything", 42}
604                 }
605             },
606             {"list", {1, 0, 2}},
607             {
608                 "object", {
609                     {"currency", "USD"},
610                     {"value", 42.99},
611                     {"", "empty string"},
612                     {"/", "slash"},
613                     {"~", "tilde"},
614                     {"~1", "tilde1"}
615                 }
616             }
617         };
618 
619         // empty json_pointer returns the root JSON-object
620         auto ptr = ""_json_pointer;
621         CHECK(j[ptr] == j);
622 
623         // simple field access
624         ptr = ptr / "pi";
625         CHECK(j[ptr] == j["pi"]);
626 
627         ptr.pop_back();
628         CHECK(j[ptr] == j);
629 
630         // object and children access
631         const std::string answer("answer");
632         ptr /= answer;
633         ptr = ptr / "everything";
634         CHECK(j[ptr] == j["answer"]["everything"]);
635 
636         ptr.pop_back();
637         ptr.pop_back();
638         CHECK(j[ptr] == j);
639 
640         CHECK(ptr / ""_json_pointer == ptr);
641         CHECK(j["/answer"_json_pointer / "/everything"_json_pointer] == j["answer"]["everything"]);
642 
643         // list children access
644         CHECK(j["/list"_json_pointer / 1] == j["list"][1]);
645 
646         // push key which has to be encoded
647         ptr /= "object";
648         ptr = ptr / "/";
649         CHECK(j[ptr] == j["object"]["/"]);
650         CHECK(ptr.to_string() == "/object/~1");
651     }
652 
653     SECTION("equality comparison")
654     {
655         const char* ptr_cpstring = "/foo/bar";
656         const char ptr_castring[] = "/foo/bar"; // NOLINT(hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
657         std::string ptr_string{"/foo/bar"};
658         auto ptr1 = json::json_pointer(ptr_string);
659         auto ptr2 = json::json_pointer(ptr_string);
660 
661         // build with C++20 to test rewritten candidates
662         // JSON_HAS_CPP_20
663 
664         CHECK(ptr1 == ptr2);
665 
666         CHECK(ptr1 == "/foo/bar");
667         CHECK(ptr1 == ptr_cpstring);
668         CHECK(ptr1 == ptr_castring);
669         CHECK(ptr1 == ptr_string);
670 
671         CHECK("/foo/bar" == ptr1);
672         CHECK(ptr_cpstring == ptr1);
673         CHECK(ptr_castring == ptr1);
674         CHECK(ptr_string == ptr1);
675 
676         CHECK_FALSE(ptr1 != ptr2);
677 
678         CHECK_FALSE(ptr1 != "/foo/bar");
679         CHECK_FALSE(ptr1 != ptr_cpstring);
680         CHECK_FALSE(ptr1 != ptr_castring);
681         CHECK_FALSE(ptr1 != ptr_string);
682 
683         CHECK_FALSE("/foo/bar" != ptr1);
684         CHECK_FALSE(ptr_cpstring != ptr1);
685         CHECK_FALSE(ptr_castring != ptr1);
686         CHECK_FALSE(ptr_string != ptr1);
687 
688         SECTION("exceptions")
689         {
690             CHECK_THROWS_WITH_AS(ptr1 == "foo",
691                                  "[json.exception.parse_error.107] parse error at byte 1: JSON pointer must be empty or begin with '/' - was: 'foo'", json::parse_error&);
692             CHECK_THROWS_WITH_AS("foo" == ptr1,
693                                  "[json.exception.parse_error.107] parse error at byte 1: JSON pointer must be empty or begin with '/' - was: 'foo'", json::parse_error&);
694             CHECK_THROWS_WITH_AS(ptr1 == "/~~",
695                                  "[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&);
696             CHECK_THROWS_WITH_AS("/~~" == ptr1,
697                                  "[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&);
698         }
699     }
700 
701     SECTION("less-than comparison")
702     {
703         auto ptr1 = json::json_pointer("/foo/a");
704         auto ptr2 = json::json_pointer("/foo/b");
705 
706         CHECK(ptr1 < ptr2);
707         CHECK_FALSE(ptr2 < ptr1);
708 
709         // build with C++20
710         // JSON_HAS_CPP_20
711 #if JSON_HAS_THREE_WAY_COMPARISON
712         CHECK((ptr1 <=> ptr2) == std::strong_ordering::less); // *NOPAD*
713         CHECK(ptr2 > ptr1);
714 #endif
715     }
716 
717     SECTION("usable as map key")
718     {
719         auto ptr = json::json_pointer("/foo");
720         std::map<json::json_pointer, int> m;
721 
722         m[ptr] = 42;
723 
724         CHECK(m.find(ptr) != m.end());
725     }
726 
727     SECTION("backwards compatibility and mixing")
728     {
729         json j = R"(
730         {
731             "foo": ["bar", "baz"]
732         }
733         )"_json;
734 
735         using nlohmann::ordered_json;
736         using json_ptr_str = nlohmann::json_pointer<std::string>;
737         using json_ptr_j = nlohmann::json_pointer<json>;
738         using json_ptr_oj = nlohmann::json_pointer<ordered_json>;
739 
740         CHECK(std::is_same<json_ptr_str::string_t, json::json_pointer::string_t>::value);
741         CHECK(std::is_same<json_ptr_str::string_t, ordered_json::json_pointer::string_t>::value);
742         CHECK(std::is_same<json_ptr_str::string_t, json_ptr_j::string_t>::value);
743         CHECK(std::is_same<json_ptr_str::string_t, json_ptr_oj::string_t>::value);
744 
745         std::string ptr_string{"/foo/0"};
746         json_ptr_str ptr{ptr_string};
747         json_ptr_j ptr_j{ptr_string};
748         json_ptr_oj ptr_oj{ptr_string};
749 
750         CHECK(j.contains(ptr));
751         CHECK(j.contains(ptr_j));
752         CHECK(j.contains(ptr_oj));
753 
754         CHECK(j.at(ptr) == j.at(ptr_j));
755         CHECK(j.at(ptr) == j.at(ptr_oj));
756 
757         CHECK(j[ptr] == j[ptr_j]);
758         CHECK(j[ptr] == j[ptr_oj]);
759 
760         CHECK(j.value(ptr, "x") == j.value(ptr_j, "x"));
761         CHECK(j.value(ptr, "x") == j.value(ptr_oj, "x"));
762 
763         CHECK(ptr == ptr_j);
764         CHECK(ptr == ptr_oj);
765         CHECK_FALSE(ptr != ptr_j);
766         CHECK_FALSE(ptr != ptr_oj);
767 
768         SECTION("equality comparison")
769         {
770             // build with C++20 to test rewritten candidates
771             // JSON_HAS_CPP_20
772 
773             CHECK(ptr == ptr_j);
774             CHECK(ptr == ptr_oj);
775             CHECK(ptr_j == ptr);
776             CHECK(ptr_j == ptr_oj);
777             CHECK(ptr_oj == ptr_j);
778             CHECK(ptr_oj == ptr);
779 
780             CHECK_FALSE(ptr != ptr_j);
781             CHECK_FALSE(ptr != ptr_oj);
782             CHECK_FALSE(ptr_j != ptr);
783             CHECK_FALSE(ptr_j != ptr_oj);
784             CHECK_FALSE(ptr_oj != ptr_j);
785             CHECK_FALSE(ptr_oj != ptr);
786         }
787     }
788 }
789