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 #include <nlohmann/json.hpp> 12 using nlohmann::json; 13 #ifdef JSON_TEST_NO_GLOBAL_UDLS 14 using namespace nlohmann::literals; // NOLINT(google-build-using-namespace) 15 #endif 16 17 #include <fstream> 18 #include "make_test_data_available.hpp" 19 20 TEST_CASE("JSON patch") 21 { 22 SECTION("examples from RFC 6902") 23 { 24 SECTION("4. Operations") 25 { 26 // the ordering of members in JSON objects is not significant: 27 json op1 = R"({ "op": "add", "path": "/a/b/c", "value": "foo" })"_json; 28 json op2 = R"({ "path": "/a/b/c", "op": "add", "value": "foo" })"_json; 29 json op3 = R"({ "value": "foo", "path": "/a/b/c", "op": "add" })"_json; 30 31 // check if the operation objects are equivalent 32 CHECK(op1 == op2); 33 CHECK(op1 == op3); 34 } 35 36 SECTION("4.1 add") 37 { 38 json patch1 = R"([{ "op": "add", "path": "/a/b", "value": [ "foo", "bar" ] }])"_json; 39 40 // However, the object itself or an array containing it does need 41 // to exist, and it remains an error for that not to be the case. 42 // For example, an "add" with a target location of "/a/b" starting 43 // with this document 44 json doc1 = R"({ "a": { "foo": 1 } })"_json; 45 46 // is not an error, because "a" exists, and "b" will be added to 47 // its value. 48 CHECK_NOTHROW(doc1.patch(patch1)); 49 auto doc1_ans = R"( 50 { 51 "a": { 52 "foo": 1, 53 "b": [ "foo", "bar" ] 54 } 55 } 56 )"_json; 57 CHECK(doc1.patch(patch1) == doc1_ans); 58 59 // It is an error in this document: 60 json doc2 = R"({ "q": { "bar": 2 } })"_json; 61 62 // because "a" does not exist. 63 CHECK_THROWS_WITH_AS(doc2.patch(patch1), "[json.exception.out_of_range.403] key 'a' not found", json::out_of_range&); 64 65 json doc3 = R"({ "a": {} })"_json; 66 json patch2 = R"([{ "op": "add", "path": "/a/b/c", "value": 1 }])"_json; 67 68 // should cause an error because "b" does not exist in doc3 69 #if JSON_DIAGNOSTICS 70 CHECK_THROWS_WITH_AS(doc3.patch(patch2), "[json.exception.out_of_range.403] (/a) key 'b' not found", json::out_of_range&); 71 #else 72 CHECK_THROWS_WITH_AS(doc3.patch(patch2), "[json.exception.out_of_range.403] key 'b' not found", json::out_of_range&); 73 #endif 74 } 75 76 SECTION("4.2 remove") 77 { 78 // If removing an element from an array, any elements above the 79 // specified index are shifted one position to the left. 80 json doc = {1, 2, 3, 4}; 81 json patch = {{{"op", "remove"}, {"path", "/1"}}}; 82 CHECK(doc.patch(patch) == json({1, 3, 4})); 83 } 84 85 SECTION("A.1. Adding an Object Member") 86 { 87 // An example target JSON document: 88 json doc = R"( 89 { "foo": "bar"} 90 )"_json; 91 92 // A JSON Patch document: 93 json patch = R"( 94 [ 95 { "op": "add", "path": "/baz", "value": "qux" } 96 ] 97 )"_json; 98 99 // The resulting JSON document: 100 json expected = R"( 101 { 102 "baz": "qux", 103 "foo": "bar" 104 } 105 )"_json; 106 107 // check if patched value is as expected 108 CHECK(doc.patch(patch) == expected); 109 110 // check roundtrip 111 CHECK(doc.patch(json::diff(doc, expected)) == expected); 112 } 113 114 SECTION("A.2. Adding an Array Element") 115 { 116 // An example target JSON document: 117 json doc = R"( 118 { "foo": [ "bar", "baz" ] } 119 )"_json; 120 121 // A JSON Patch document: 122 json patch = R"( 123 [ 124 { "op": "add", "path": "/foo/1", "value": "qux" } 125 ] 126 )"_json; 127 128 // The resulting JSON document: 129 json expected = R"( 130 { "foo": [ "bar", "qux", "baz" ] } 131 )"_json; 132 133 // check if patched value is as expected 134 CHECK(doc.patch(patch) == expected); 135 136 // check roundtrip 137 CHECK(doc.patch(json::diff(doc, expected)) == expected); 138 } 139 140 SECTION("A.3. Removing an Object Member") 141 { 142 // An example target JSON document: 143 json doc = R"( 144 { 145 "baz": "qux", 146 "foo": "bar" 147 } 148 )"_json; 149 150 // A JSON Patch document: 151 json patch = R"( 152 [ 153 { "op": "remove", "path": "/baz" } 154 ] 155 )"_json; 156 157 // The resulting JSON document: 158 json expected = R"( 159 { "foo": "bar" } 160 )"_json; 161 162 // check if patched value is as expected 163 CHECK(doc.patch(patch) == expected); 164 165 // check roundtrip 166 CHECK(doc.patch(json::diff(doc, expected)) == expected); 167 } 168 169 SECTION("A.4. Removing an Array Element") 170 { 171 // An example target JSON document: 172 json doc = R"( 173 { "foo": [ "bar", "qux", "baz" ] } 174 )"_json; 175 176 // A JSON Patch document: 177 json patch = R"( 178 [ 179 { "op": "remove", "path": "/foo/1" } 180 ] 181 )"_json; 182 183 // The resulting JSON document: 184 json expected = R"( 185 { "foo": [ "bar", "baz" ] } 186 )"_json; 187 188 // check if patched value is as expected 189 CHECK(doc.patch(patch) == expected); 190 191 // check roundtrip 192 CHECK(doc.patch(json::diff(doc, expected)) == expected); 193 } 194 195 SECTION("A.5. Replacing a Value") 196 { 197 // An example target JSON document: 198 json doc = R"( 199 { 200 "baz": "qux", 201 "foo": "bar" 202 } 203 )"_json; 204 205 // A JSON Patch document: 206 json patch = R"( 207 [ 208 { "op": "replace", "path": "/baz", "value": "boo" } 209 ] 210 )"_json; 211 212 json expected = R"( 213 { 214 "baz": "boo", 215 "foo": "bar" 216 } 217 )"_json; 218 219 // check if patched value is as expected 220 CHECK(doc.patch(patch) == expected); 221 222 // check roundtrip 223 CHECK(doc.patch(json::diff(doc, expected)) == expected); 224 } 225 226 SECTION("A.6. Moving a Value") 227 { 228 // An example target JSON document: 229 json doc = R"( 230 { 231 "foo": { 232 "bar": "baz", 233 "waldo": "fred" 234 }, 235 "qux": { 236 "corge": "grault" 237 } 238 } 239 )"_json; 240 241 // A JSON Patch document: 242 json patch = R"( 243 [ 244 { "op": "move", "from": "/foo/waldo", "path": "/qux/thud" } 245 ] 246 )"_json; 247 248 // The resulting JSON document: 249 json expected = R"( 250 { 251 "foo": { 252 "bar": "baz" 253 }, 254 "qux": { 255 "corge": "grault", 256 "thud": "fred" 257 } 258 } 259 )"_json; 260 261 // check if patched value is as expected 262 CHECK(doc.patch(patch) == expected); 263 264 // check roundtrip 265 CHECK(doc.patch(json::diff(doc, expected)) == expected); 266 } 267 268 SECTION("A.7. Moving a Value") 269 { 270 // An example target JSON document: 271 json doc = R"( 272 { "foo": [ "all", "grass", "cows", "eat" ] } 273 )"_json; 274 275 // A JSON Patch document: 276 json patch = R"( 277 [ 278 { "op": "move", "from": "/foo/1", "path": "/foo/3" } 279 ] 280 )"_json; 281 282 // The resulting JSON document: 283 json expected = R"( 284 { "foo": [ "all", "cows", "eat", "grass" ] } 285 )"_json; 286 287 // check if patched value is as expected 288 CHECK(doc.patch(patch) == expected); 289 290 // check roundtrip 291 CHECK(doc.patch(json::diff(doc, expected)) == expected); 292 } 293 294 SECTION("A.8. Testing a Value: Success") 295 { 296 // An example target JSON document: 297 json doc = R"( 298 { 299 "baz": "qux", 300 "foo": [ "a", 2, "c" ] 301 } 302 )"_json; 303 304 // A JSON Patch document that will result in successful evaluation: 305 json patch = R"( 306 [ 307 { "op": "test", "path": "/baz", "value": "qux" }, 308 { "op": "test", "path": "/foo/1", "value": 2 } 309 ] 310 )"_json; 311 312 // check if evaluation does not throw 313 CHECK_NOTHROW(doc.patch(patch)); 314 // check if patched document is unchanged 315 CHECK(doc.patch(patch) == doc); 316 } 317 318 SECTION("A.9. Testing a Value: Error") 319 { 320 // An example target JSON document: 321 json doc = R"( 322 { "baz": "qux" } 323 )"_json; 324 325 // A JSON Patch document that will result in an error condition: 326 json patch = R"( 327 [ 328 { "op": "test", "path": "/baz", "value": "bar" } 329 ] 330 )"_json; 331 332 // check that evaluation throws 333 CHECK_THROWS_AS(doc.patch(patch), json::other_error&); 334 #if JSON_DIAGNOSTICS 335 CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (/0) unsuccessful: " + patch[0].dump()); 336 #else 337 CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] unsuccessful: " + patch[0].dump()); 338 #endif 339 } 340 341 SECTION("A.10. Adding a Nested Member Object") 342 { 343 // An example target JSON document: 344 json doc = R"( 345 { "foo": "bar" } 346 )"_json; 347 348 // A JSON Patch document: 349 json patch = R"( 350 [ 351 { "op": "add", "path": "/child", "value": { "grandchild": { } } } 352 ] 353 )"_json; 354 355 // The resulting JSON document: 356 json expected = R"( 357 { 358 "foo": "bar", 359 "child": { 360 "grandchild": { 361 } 362 } 363 } 364 )"_json; 365 366 // check if patched value is as expected 367 CHECK(doc.patch(patch) == expected); 368 369 // check roundtrip 370 CHECK(doc.patch(json::diff(doc, expected)) == expected); 371 } 372 373 SECTION("A.11. Ignoring Unrecognized Elements") 374 { 375 // An example target JSON document: 376 json doc = R"( 377 { "foo": "bar" } 378 )"_json; 379 380 // A JSON Patch document: 381 json patch = R"( 382 [ 383 { "op": "add", "path": "/baz", "value": "qux", "xyz": 123 } 384 ] 385 )"_json; 386 387 json expected = R"( 388 { 389 "foo": "bar", 390 "baz": "qux" 391 } 392 )"_json; 393 394 // check if patched value is as expected 395 CHECK(doc.patch(patch) == expected); 396 397 // check roundtrip 398 CHECK(doc.patch(json::diff(doc, expected)) == expected); 399 } 400 401 SECTION("A.12. Adding to a Nonexistent Target") 402 { 403 // An example target JSON document: 404 json doc = R"( 405 { "foo": "bar" } 406 )"_json; 407 408 // A JSON Patch document: 409 json patch = R"( 410 [ 411 { "op": "add", "path": "/baz/bat", "value": "qux" } 412 ] 413 )"_json; 414 415 // This JSON Patch document, applied to the target JSON document 416 // above, would result in an error (therefore, it would not be 417 // applied), because the "add" operation's target location that 418 // references neither the root of the document, nor a member of 419 // an existing object, nor a member of an existing array. 420 421 CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.403] key 'baz' not found", json::out_of_range&); 422 } 423 424 // A.13. Invalid JSON Patch Document 425 // not applicable 426 427 SECTION("A.14. Escape Ordering") 428 { 429 // An example target JSON document: 430 json doc = R"( 431 { 432 "/": 9, 433 "~1": 10 434 } 435 )"_json; 436 437 // A JSON Patch document: 438 json patch = R"( 439 [ 440 {"op": "test", "path": "/~01", "value": 10} 441 ] 442 )"_json; 443 444 json expected = R"( 445 { 446 "/": 9, 447 "~1": 10 448 } 449 )"_json; 450 451 // check if patched value is as expected 452 CHECK(doc.patch(patch) == expected); 453 454 // check roundtrip 455 CHECK(doc.patch(json::diff(doc, expected)) == expected); 456 } 457 458 SECTION("A.15. Comparing Strings and Numbers") 459 { 460 // An example target JSON document: 461 json doc = R"( 462 { 463 "/": 9, 464 "~1": 10 465 } 466 )"_json; 467 468 // A JSON Patch document that will result in an error condition: 469 json patch = R"( 470 [ 471 {"op": "test", "path": "/~01", "value": "10"} 472 ] 473 )"_json; 474 475 // check that evaluation throws 476 CHECK_THROWS_AS(doc.patch(patch), json::other_error&); 477 #if JSON_DIAGNOSTICS 478 CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (/0) unsuccessful: " + patch[0].dump()); 479 #else 480 CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] unsuccessful: " + patch[0].dump()); 481 #endif 482 } 483 484 SECTION("A.16. Adding an Array Value") 485 { 486 // An example target JSON document: 487 json doc = R"( 488 { "foo": ["bar"] } 489 )"_json; 490 491 // A JSON Patch document: 492 json patch = R"( 493 [ 494 { "op": "add", "path": "/foo/-", "value": ["abc", "def"] } 495 ] 496 )"_json; 497 498 // The resulting JSON document: 499 json expected = R"( 500 { "foo": ["bar", ["abc", "def"]] } 501 )"_json; 502 503 // check if patched value is as expected 504 CHECK(doc.patch(patch) == expected); 505 506 // check roundtrip 507 CHECK(doc.patch(json::diff(doc, expected)) == expected); 508 } 509 } 510 511 SECTION("own examples") 512 { 513 SECTION("add") 514 { 515 SECTION("add to the root element") 516 { 517 // If the path is the root of the target document - the 518 // specified value becomes the entire content of the target 519 // document. 520 521 // An example target JSON document: 522 json doc = 17; 523 524 // A JSON Patch document: 525 json patch = R"( 526 [ 527 { "op": "add", "path": "", "value": [1,2,3] } 528 ] 529 )"_json; 530 531 // The resulting JSON document: 532 json expected = {1, 2, 3}; 533 534 // check if patched value is as expected 535 CHECK(doc.patch(patch) == expected); 536 537 // check roundtrip 538 CHECK(doc.patch(json::diff(doc, expected)) == expected); 539 } 540 541 SECTION("add to end of the array") 542 { 543 // The specified index MUST NOT be greater than the number of 544 // elements in the array. The example below uses and index of 545 // exactly the number of elements in the array which is legal. 546 547 // An example target JSON document: 548 json doc = {0, 1, 2}; 549 550 // A JSON Patch document: 551 json patch = R"( 552 [ 553 { "op": "add", "path": "/3", "value": 3 } 554 ] 555 )"_json; 556 557 // The resulting JSON document: 558 json expected = {0, 1, 2, 3}; 559 560 // check if patched value is as expected 561 CHECK(doc.patch(patch) == expected); 562 563 // check roundtrip 564 CHECK(doc.patch(json::diff(doc, expected)) == expected); 565 } 566 } 567 568 SECTION("copy") 569 { 570 // An example target JSON document: 571 json doc = R"( 572 { 573 "foo": { 574 "bar": "baz", 575 "waldo": "fred" 576 }, 577 "qux": { 578 "corge": "grault" 579 } 580 } 581 )"_json; 582 583 // A JSON Patch document: 584 json patch = R"( 585 [ 586 { "op": "copy", "from": "/foo/waldo", "path": "/qux/thud" } 587 ] 588 )"_json; 589 590 // The resulting JSON document: 591 json expected = R"( 592 { 593 "foo": { 594 "bar": "baz", 595 "waldo": "fred" 596 }, 597 "qux": { 598 "corge": "grault", 599 "thud": "fred" 600 } 601 } 602 )"_json; 603 604 // check if patched value is as expected 605 CHECK(doc.patch(patch) == expected); 606 607 // check roundtrip 608 CHECK(doc.patch(json::diff(doc, expected)) == expected); 609 } 610 611 SECTION("replace") 612 { 613 json j = "string"; 614 json patch = {{{"op", "replace"}, {"path", ""}, {"value", 1}}}; 615 CHECK(j.patch(patch) == json(1)); 616 } 617 618 SECTION("documentation GIF") 619 { 620 { 621 // a JSON patch 622 json p1 = R"( 623 [{"op": "add", "path": "/GB", "value": "London"}] 624 )"_json; 625 626 // a JSON value 627 json source = R"( 628 {"D": "Berlin", "F": "Paris"} 629 )"_json; 630 631 // apply the patch 632 json target = source.patch(p1); 633 // target = { "D": "Berlin", "F": "Paris", "GB": "London" } 634 CHECK(target == R"({ "D": "Berlin", "F": "Paris", "GB": "London" })"_json); 635 636 // create a diff from two JSONs 637 json p2 = json::diff(target, source); // NOLINT(readability-suspicious-call-argument) 638 // p2 = [{"op": "delete", "path": "/GB"}] 639 CHECK(p2 == R"([{"op":"remove","path":"/GB"}])"_json); 640 } 641 { 642 // a JSON value 643 json j = {"good", "bad", "ugly"}; 644 645 // a JSON pointer 646 auto ptr = json::json_pointer("/2"); 647 648 // use to access elements 649 j[ptr] = {{"it", "cattivo"}}; 650 CHECK(j == R"(["good","bad",{"it":"cattivo"}])"_json); 651 652 // use user-defined string literal 653 j["/2/en"_json_pointer] = "ugly"; 654 CHECK(j == R"(["good","bad",{"en":"ugly","it":"cattivo"}])"_json); 655 656 json flat = j.flatten(); 657 CHECK(flat == R"({"/0":"good","/1":"bad","/2/en":"ugly","/2/it":"cattivo"})"_json); 658 } 659 } 660 } 661 662 SECTION("errors") 663 { 664 SECTION("unknown operation") 665 { 666 SECTION("not an array") 667 { 668 json j; 669 json patch = {{"op", "add"}, {"path", ""}, {"value", 1}}; 670 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.104] parse error: JSON patch must be an array of objects", json::parse_error&); 671 } 672 673 SECTION("not an array of objects") 674 { 675 json j; 676 json patch = {"op", "add", "path", "", "value", 1}; 677 #if JSON_DIAGNOSTICS 678 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.104] parse error: (/0) JSON patch must be an array of objects", json::parse_error&); 679 #else 680 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.104] parse error: JSON patch must be an array of objects", json::parse_error&); 681 #endif 682 } 683 684 SECTION("missing 'op'") 685 { 686 json j; 687 json patch = {{{"foo", "bar"}}}; 688 #if JSON_DIAGNOSTICS 689 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation must have member 'op'", json::parse_error&); 690 #else 691 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation must have member 'op'", json::parse_error&); 692 #endif 693 } 694 695 SECTION("non-string 'op'") 696 { 697 json j; 698 json patch = {{{"op", 1}}}; 699 #if JSON_DIAGNOSTICS 700 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation must have string member 'op'", json::parse_error&); 701 #else 702 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation must have string member 'op'", json::parse_error&); 703 #endif 704 } 705 706 SECTION("invalid operation") 707 { 708 json j; 709 json patch = {{{"op", "foo"}, {"path", ""}}}; 710 #if JSON_DIAGNOSTICS 711 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation value 'foo' is invalid", json::parse_error&); 712 #else 713 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation value 'foo' is invalid", json::parse_error&); 714 #endif 715 } 716 } 717 718 SECTION("add") 719 { 720 SECTION("missing 'path'") 721 { 722 json j; 723 json patch = {{{"op", "add"}}}; 724 #if JSON_DIAGNOSTICS 725 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'add' must have member 'path'", json::parse_error&); 726 #else 727 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'add' must have member 'path'", json::parse_error&); 728 #endif 729 } 730 731 SECTION("non-string 'path'") 732 { 733 json j; 734 json patch = {{{"op", "add"}, {"path", 1}}}; 735 #if JSON_DIAGNOSTICS 736 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'add' must have string member 'path'", json::parse_error&); 737 #else 738 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'add' must have string member 'path'", json::parse_error&); 739 #endif 740 } 741 742 SECTION("missing 'value'") 743 { 744 json j; 745 json patch = {{{"op", "add"}, {"path", ""}}}; 746 #if JSON_DIAGNOSTICS 747 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'add' must have member 'value'", json::parse_error&); 748 #else 749 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'add' must have member 'value'", json::parse_error&); 750 #endif 751 } 752 753 SECTION("invalid array index") 754 { 755 json j = {1, 2}; 756 json patch = {{{"op", "add"}, {"path", "/4"}, {"value", 4}}}; 757 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.401] array index 4 is out of range", json::out_of_range&); 758 } 759 } 760 761 SECTION("remove") 762 { 763 SECTION("missing 'path'") 764 { 765 json j; 766 json patch = {{{"op", "remove"}}}; 767 #if JSON_DIAGNOSTICS 768 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'remove' must have member 'path'", json::parse_error&); 769 #else 770 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'remove' must have member 'path'", json::parse_error&); 771 #endif 772 } 773 774 SECTION("non-string 'path'") 775 { 776 json j; 777 json patch = {{{"op", "remove"}, {"path", 1}}}; 778 #if JSON_DIAGNOSTICS 779 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'remove' must have string member 'path'", json::parse_error&); 780 #else 781 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'remove' must have string member 'path'", json::parse_error&); 782 #endif 783 } 784 785 SECTION("nonexisting target location (array)") 786 { 787 json j = {1, 2, 3}; 788 json patch = {{{"op", "remove"}, {"path", "/17"}}}; 789 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.401] array index 17 is out of range", json::out_of_range&); 790 } 791 792 SECTION("nonexisting target location (object)") 793 { 794 json j = {{"foo", 1}, {"bar", 2}}; 795 json patch = {{{"op", "remove"}, {"path", "/baz"}}}; 796 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.403] key 'baz' not found", json::out_of_range&); 797 } 798 799 SECTION("root element as target location") 800 { 801 json j = "string"; 802 json patch = {{{"op", "remove"}, {"path", ""}}}; 803 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.405] JSON pointer has no parent", json::out_of_range&); 804 } 805 } 806 807 SECTION("replace") 808 { 809 SECTION("missing 'path'") 810 { 811 json j; 812 json patch = {{{"op", "replace"}}}; 813 #if JSON_DIAGNOSTICS 814 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'replace' must have member 'path'", json::parse_error&); 815 #else 816 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'replace' must have member 'path'", json::parse_error&); 817 #endif 818 } 819 820 SECTION("non-string 'path'") 821 { 822 json j; 823 json patch = {{{"op", "replace"}, {"path", 1}}}; 824 #if JSON_DIAGNOSTICS 825 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'replace' must have string member 'path'", json::parse_error&); 826 #else 827 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'replace' must have string member 'path'", json::parse_error&); 828 #endif 829 } 830 831 SECTION("missing 'value'") 832 { 833 json j; 834 json patch = {{{"op", "replace"}, {"path", ""}}}; 835 #if JSON_DIAGNOSTICS 836 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'replace' must have member 'value'", json::parse_error&); 837 #else 838 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'replace' must have member 'value'", json::parse_error&); 839 #endif 840 } 841 842 SECTION("nonexisting target location (array)") 843 { 844 json j = {1, 2, 3}; 845 json patch = {{{"op", "replace"}, {"path", "/17"}, {"value", 19}}}; 846 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.401] array index 17 is out of range", json::out_of_range&); 847 } 848 849 SECTION("nonexisting target location (object)") 850 { 851 json j = {{"foo", 1}, {"bar", 2}}; 852 json patch = {{{"op", "replace"}, {"path", "/baz"}, {"value", 3}}}; 853 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.403] key 'baz' not found", json::out_of_range&); 854 } 855 } 856 857 SECTION("move") 858 { 859 SECTION("missing 'path'") 860 { 861 json j; 862 json patch = {{{"op", "move"}}}; 863 #if JSON_DIAGNOSTICS 864 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'move' must have member 'path'", json::parse_error&); 865 #else 866 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'move' must have member 'path'", json::parse_error&); 867 #endif 868 } 869 870 SECTION("non-string 'path'") 871 { 872 json j; 873 json patch = {{{"op", "move"}, {"path", 1}}}; 874 #if JSON_DIAGNOSTICS 875 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'move' must have string member 'path'", json::parse_error&); 876 #else 877 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'move' must have string member 'path'", json::parse_error&); 878 #endif 879 } 880 881 SECTION("missing 'from'") 882 { 883 json j; 884 json patch = {{{"op", "move"}, {"path", ""}}}; 885 CHECK_THROWS_AS(j.patch(patch), json::parse_error&); 886 #if JSON_DIAGNOSTICS 887 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'move' must have member 'from'", json::parse_error&); 888 #else 889 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'move' must have member 'from'", json::parse_error&); 890 #endif 891 } 892 893 SECTION("non-string 'from'") 894 { 895 json j; 896 json patch = {{{"op", "move"}, {"path", ""}, {"from", 1}}}; 897 CHECK_THROWS_AS(j.patch(patch), json::parse_error&); 898 #if JSON_DIAGNOSTICS 899 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'move' must have string member 'from'", json::parse_error&); 900 #else 901 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'move' must have string member 'from'", json::parse_error&); 902 #endif 903 } 904 905 SECTION("nonexisting from location (array)") 906 { 907 json j = {1, 2, 3}; 908 json patch = {{{"op", "move"}, {"path", "/0"}, {"from", "/5"}}}; 909 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.401] array index 5 is out of range", json::out_of_range&); 910 } 911 912 SECTION("nonexisting from location (object)") 913 { 914 json j = {{"foo", 1}, {"bar", 2}}; 915 json patch = {{{"op", "move"}, {"path", "/baz"}, {"from", "/baz"}}}; 916 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.403] key 'baz' not found", json::out_of_range&); 917 } 918 } 919 920 SECTION("copy") 921 { 922 SECTION("missing 'path'") 923 { 924 json j; 925 json patch = {{{"op", "copy"}}}; 926 #if JSON_DIAGNOSTICS 927 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'copy' must have member 'path'", json::parse_error&); 928 #else 929 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'copy' must have member 'path'", json::parse_error&); 930 #endif 931 } 932 933 SECTION("non-string 'path'") 934 { 935 json j; 936 json patch = {{{"op", "copy"}, {"path", 1}}}; 937 #if JSON_DIAGNOSTICS 938 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'copy' must have string member 'path'", json::parse_error&); 939 #else 940 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'copy' must have string member 'path'", json::parse_error&); 941 #endif 942 } 943 944 SECTION("missing 'from'") 945 { 946 json j; 947 json patch = {{{"op", "copy"}, {"path", ""}}}; 948 #if JSON_DIAGNOSTICS 949 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'copy' must have member 'from'", json::parse_error&); 950 #else 951 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'copy' must have member 'from'", json::parse_error&); 952 #endif 953 } 954 955 SECTION("non-string 'from'") 956 { 957 json j; 958 json patch = {{{"op", "copy"}, {"path", ""}, {"from", 1}}}; 959 #if JSON_DIAGNOSTICS 960 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'copy' must have string member 'from'", json::parse_error&); 961 #else 962 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'copy' must have string member 'from'", json::parse_error&); 963 #endif 964 } 965 966 SECTION("nonexisting from location (array)") 967 { 968 json j = {1, 2, 3}; 969 json patch = {{{"op", "copy"}, {"path", "/0"}, {"from", "/5"}}}; 970 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.401] array index 5 is out of range", json::out_of_range&); 971 } 972 973 SECTION("nonexisting from location (object)") 974 { 975 json j = {{"foo", 1}, {"bar", 2}}; 976 json patch = {{{"op", "copy"}, {"path", "/fob"}, {"from", "/baz"}}}; 977 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.out_of_range.403] key 'baz' not found", json::out_of_range&); 978 } 979 } 980 981 SECTION("test") 982 { 983 SECTION("missing 'path'") 984 { 985 json j; 986 json patch = {{{"op", "test"}}}; 987 #if JSON_DIAGNOSTICS 988 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'test' must have member 'path'", json::parse_error&); 989 #else 990 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'test' must have member 'path'", json::parse_error&); 991 #endif 992 } 993 994 SECTION("non-string 'path'") 995 { 996 json j; 997 json patch = {{{"op", "test"}, {"path", 1}}}; 998 #if JSON_DIAGNOSTICS 999 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'test' must have string member 'path'", json::parse_error&); 1000 #else 1001 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'test' must have string member 'path'", json::parse_error&); 1002 #endif 1003 } 1004 1005 SECTION("missing 'value'") 1006 { 1007 json j; 1008 json patch = {{{"op", "test"}, {"path", ""}}}; 1009 #if JSON_DIAGNOSTICS 1010 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: (/0) operation 'test' must have member 'value'", json::parse_error&); 1011 #else 1012 CHECK_THROWS_WITH_AS(j.patch(patch), "[json.exception.parse_error.105] parse error: operation 'test' must have member 'value'", json::parse_error&); 1013 #endif 1014 } 1015 } 1016 } 1017 1018 SECTION("Examples from jsonpatch.com") 1019 { 1020 SECTION("Simple Example") 1021 { 1022 // The original document 1023 json doc = R"( 1024 { 1025 "baz": "qux", 1026 "foo": "bar" 1027 } 1028 )"_json; 1029 1030 // The patch 1031 json patch = R"( 1032 [ 1033 { "op": "replace", "path": "/baz", "value": "boo" }, 1034 { "op": "add", "path": "/hello", "value": ["world"] }, 1035 { "op": "remove", "path": "/foo"} 1036 ] 1037 )"_json; 1038 1039 // The result 1040 json result = R"( 1041 { 1042 "baz": "boo", 1043 "hello": ["world"] 1044 } 1045 )"_json; 1046 1047 // check if patched value is as expected 1048 CHECK(doc.patch(patch) == result); 1049 1050 // check roundtrip 1051 CHECK(doc.patch(json::diff(doc, result)) == result); 1052 } 1053 1054 SECTION("Operations") 1055 { 1056 // The original document 1057 json doc = R"( 1058 { 1059 "biscuits": [ 1060 {"name":"Digestive"}, 1061 {"name": "Choco Liebniz"} 1062 ] 1063 } 1064 )"_json; 1065 1066 SECTION("add") 1067 { 1068 // The patch 1069 json patch = R"( 1070 [ 1071 {"op": "add", "path": "/biscuits/1", "value": {"name": "Ginger Nut"}} 1072 ] 1073 )"_json; 1074 1075 // The result 1076 json result = R"( 1077 { 1078 "biscuits": [ 1079 {"name": "Digestive"}, 1080 {"name": "Ginger Nut"}, 1081 {"name": "Choco Liebniz"} 1082 ] 1083 } 1084 )"_json; 1085 1086 // check if patched value is as expected 1087 CHECK(doc.patch(patch) == result); 1088 1089 // check roundtrip 1090 CHECK(doc.patch(json::diff(doc, result)) == result); 1091 } 1092 1093 SECTION("remove") 1094 { 1095 // The patch 1096 json patch = R"( 1097 [ 1098 {"op": "remove", "path": "/biscuits"} 1099 ] 1100 )"_json; 1101 1102 // The result 1103 json result = R"( 1104 {} 1105 )"_json; 1106 1107 // check if patched value is as expected 1108 CHECK(doc.patch(patch) == result); 1109 1110 // check roundtrip 1111 CHECK(doc.patch(json::diff(doc, result)) == result); 1112 } 1113 1114 SECTION("replace") 1115 { 1116 // The patch 1117 json patch = R"( 1118 [ 1119 {"op": "replace", "path": "/biscuits/0/name", "value": "Chocolate Digestive"} 1120 ] 1121 )"_json; 1122 1123 // The result 1124 json result = R"( 1125 { 1126 "biscuits": [ 1127 {"name": "Chocolate Digestive"}, 1128 {"name": "Choco Liebniz"} 1129 ] 1130 } 1131 )"_json; 1132 1133 // check if patched value is as expected 1134 CHECK(doc.patch(patch) == result); 1135 1136 // check roundtrip 1137 CHECK(doc.patch(json::diff(doc, result)) == result); 1138 } 1139 1140 SECTION("copy") 1141 { 1142 // The patch 1143 json patch = R"( 1144 [ 1145 {"op": "copy", "from": "/biscuits/0", "path": "/best_biscuit"} 1146 ] 1147 )"_json; 1148 1149 // The result 1150 json result = R"( 1151 { 1152 "biscuits": [ 1153 {"name": "Digestive"}, 1154 {"name": "Choco Liebniz"} 1155 ], 1156 "best_biscuit": { 1157 "name": "Digestive" 1158 } 1159 } 1160 )"_json; 1161 1162 // check if patched value is as expected 1163 CHECK(doc.patch(patch) == result); 1164 1165 // check roundtrip 1166 CHECK(doc.patch(json::diff(doc, result)) == result); 1167 } 1168 1169 SECTION("move") 1170 { 1171 // The patch 1172 json patch = R"( 1173 [ 1174 {"op": "move", "from": "/biscuits", "path": "/cookies"} 1175 ] 1176 )"_json; 1177 1178 // The result 1179 json result = R"( 1180 { 1181 "cookies": [ 1182 {"name": "Digestive"}, 1183 {"name": "Choco Liebniz"} 1184 ] 1185 } 1186 )"_json; 1187 1188 // check if patched value is as expected 1189 CHECK(doc.patch(patch) == result); 1190 1191 // check roundtrip 1192 CHECK(doc.patch(json::diff(doc, result)) == result); 1193 } 1194 1195 SECTION("test") 1196 { 1197 // The patch 1198 json patch = R"( 1199 [ 1200 {"op": "test", "path": "/best_biscuit/name", "value": "Choco Liebniz"} 1201 ] 1202 )"_json; 1203 1204 // the test will fail 1205 CHECK_THROWS_AS(doc.patch(patch), json::other_error&); 1206 #if JSON_DIAGNOSTICS 1207 CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] (/0) unsuccessful: " + patch[0].dump()); 1208 #else 1209 CHECK_THROWS_WITH_STD_STR(doc.patch(patch), "[json.exception.other_error.501] unsuccessful: " + patch[0].dump()); 1210 #endif 1211 } 1212 } 1213 } 1214 1215 SECTION("Examples from bruth.github.io/jsonpatch-js") 1216 { 1217 SECTION("add") 1218 { 1219 CHECK(R"( {} )"_json.patch( 1220 R"( [{"op": "add", "path": "/foo", "value": "bar"}] )"_json 1221 ) == R"( {"foo": "bar"} )"_json); 1222 1223 CHECK(R"( {"foo": [1, 3]} )"_json.patch( 1224 R"( [{"op": "add", "path": "/foo", "value": "bar"}] )"_json 1225 ) == R"( {"foo": "bar"} )"_json); 1226 1227 CHECK(R"( {"foo": [{}]} )"_json.patch( 1228 R"( [{"op": "add", "path": "/foo/0/bar", "value": "baz"}] )"_json 1229 ) == R"( {"foo": [{"bar": "baz"}]} )"_json); 1230 } 1231 1232 SECTION("remove") 1233 { 1234 CHECK(R"( {"foo": "bar"} )"_json.patch( 1235 R"( [{"op": "remove", "path": "/foo"}] )"_json 1236 ) == R"( {} )"_json); 1237 1238 CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch( 1239 R"( [{"op": "remove", "path": "/foo/1"}] )"_json 1240 ) == R"( {"foo": [1, 3]} )"_json); 1241 1242 CHECK(R"( {"foo": [{"bar": "baz"}]} )"_json.patch( 1243 R"( [{"op": "remove", "path": "/foo/0/bar"}] )"_json 1244 ) == R"( {"foo": [{}]} )"_json); 1245 } 1246 1247 SECTION("replace") 1248 { 1249 CHECK(R"( {"foo": "bar"} )"_json.patch( 1250 R"( [{"op": "replace", "path": "/foo", "value": 1}] )"_json 1251 ) == R"( {"foo": 1} )"_json); 1252 1253 CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch( 1254 R"( [{"op": "replace", "path": "/foo/1", "value": 4}] )"_json 1255 ) == R"( {"foo": [1, 4, 3]} )"_json); 1256 1257 CHECK(R"( {"foo": [{"bar": "baz"}]} )"_json.patch( 1258 R"( [{"op": "replace", "path": "/foo/0/bar", "value": 1}] )"_json 1259 ) == R"( {"foo": [{"bar": 1}]} )"_json); 1260 } 1261 1262 SECTION("move") 1263 { 1264 CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch( 1265 R"( [{"op": "move", "from": "/foo", "path": "/bar"}] )"_json 1266 ) == R"( {"bar": [1, 2, 3]} )"_json); 1267 } 1268 1269 SECTION("copy") 1270 { 1271 CHECK(R"( {"foo": [1, 2, 3]} )"_json.patch( 1272 R"( [{"op": "copy", "from": "/foo/1", "path": "/bar"}] )"_json 1273 ) == R"( {"foo": [1, 2, 3], "bar": 2} )"_json); 1274 } 1275 1276 SECTION("copy") 1277 { 1278 CHECK_NOTHROW(R"( {"foo": "bar"} )"_json.patch( 1279 R"( [{"op": "test", "path": "/foo", "value": "bar"}] )"_json)); 1280 } 1281 } 1282 1283 SECTION("Tests from github.com/json-patch/json-patch-tests") 1284 { 1285 for (const auto* filename : 1286 { 1287 TEST_DATA_DIRECTORY "/json-patch-tests/spec_tests.json", 1288 TEST_DATA_DIRECTORY "/json-patch-tests/tests.json" 1289 }) 1290 { 1291 CAPTURE(filename) 1292 std::ifstream f(filename); 1293 json suite = json::parse(f); 1294 1295 for (const auto& test : suite) 1296 { 1297 INFO_WITH_TEMP(test.value("comment", "")); 1298 1299 // skip tests marked as disabled 1300 if (test.value("disabled", false)) 1301 { 1302 continue; 1303 } 1304 1305 const auto& doc = test["doc"]; 1306 const auto& patch = test["patch"]; 1307 1308 if (test.count("error") == 0) 1309 { 1310 // if an expected value is given, use it; use doc otherwise 1311 const auto& expected = test.value("expected", doc); 1312 CHECK(doc.patch(patch) == expected); 1313 } 1314 else 1315 { 1316 CHECK_THROWS(doc.patch(patch)); 1317 } 1318 } 1319 } 1320 } 1321 } 1322