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