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