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>
13using 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
21TEST_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