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