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