• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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