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