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