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