File: json-patch.md

package info (click to toggle)
glaze 6.4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 7,312 kB
  • sloc: cpp: 109,539; sh: 99; ansic: 26; makefile: 13
file content (258 lines) | stat: -rw-r--r-- 6,877 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# JSON Patch (RFC 6902)

Glaze provides full support for [JSON Patch (RFC 6902)](https://datatracker.ietf.org/doc/html/rfc6902), a format for describing changes to JSON documents. JSON Patch works with [glz::generic](./generic-json.md) for runtime JSON manipulation.

> **See also:** [JSON Merge Patch (RFC 7386)](./json-merge-patch.md) for a simpler alternative when you don't need fine-grained control over array elements or explicit null values.

## Include

```c++
#include "glaze/json/patch.hpp"
```

## Basic Usage

### Applying a Patch

Use `glz::patch()` to apply a patch document to a JSON value:

```c++
auto doc = glz::read_json<glz::generic>(R"({"a": 1, "b": 2})");

glz::patch_document ops = {
   {glz::patch_op_type::replace, "/a", glz::generic(10.0), std::nullopt},
   {glz::patch_op_type::add, "/c", glz::generic(3.0), std::nullopt}
};

auto ec = glz::patch(*doc, ops);
if (!ec) {
   // doc is now {"a": 10, "b": 2, "c": 3}
}
```

### Generating a Patch

Use `glz::diff()` to generate a patch that transforms one document into another:

```c++
auto source = glz::read_json<glz::generic>(R"({"a": 1, "b": 2})");
auto target = glz::read_json<glz::generic>(R"({"a": 1, "c": 3})");

auto patch = glz::diff(*source, *target);
if (patch) {
   // patch contains:
   // - remove "/b"
   // - add "/c" with value 3
}
```

## Patch Operations

JSON Patch supports six operations defined by RFC 6902:

### add

Adds a value at the specified path. If the path points to an array index, the value is inserted at that position.

```c++
// Add to object
glz::patch_op{glz::patch_op_type::add, "/newkey", glz::generic("value"), std::nullopt}

// Insert into array at index 1
glz::patch_op{glz::patch_op_type::add, "/array/1", glz::generic(42.0), std::nullopt}

// Append to array using "-"
glz::patch_op{glz::patch_op_type::add, "/array/-", glz::generic(99.0), std::nullopt}
```

### remove

Removes the value at the specified path.

```c++
glz::patch_op{glz::patch_op_type::remove, "/keytoremove", std::nullopt, std::nullopt}
```

### replace

Replaces the value at the specified path. The path must exist.

```c++
glz::patch_op{glz::patch_op_type::replace, "/existing", glz::generic("newvalue"), std::nullopt}
```

### move

Moves a value from one location to another. Equivalent to remove + add.

```c++
// Move value from /a to /b
glz::patch_op{glz::patch_op_type::move, "/b", std::nullopt, "/a"}
```

### copy

Copies a value from one location to another.

```c++
// Copy value from /a to /b
glz::patch_op{glz::patch_op_type::copy, "/b", std::nullopt, "/a"}
```

### test

Tests that the value at the specified path equals the given value. If the test fails, the entire patch operation fails.

```c++
// Verify /version equals 1 before proceeding
glz::patch_op{glz::patch_op_type::test, "/version", glz::generic(1.0), std::nullopt}
```

## Convenience Functions

### patched() - Non-mutating Patch

Returns a new document with the patch applied, leaving the original unchanged:

```c++
auto doc = glz::read_json<glz::generic>(R"({"a": 1})");
glz::patch_document ops = {{glz::patch_op_type::add, "/b", glz::generic(2.0), std::nullopt}};

auto result = glz::patched(*doc, ops);
if (result) {
   // *result is {"a": 1, "b": 2}
   // *doc is still {"a": 1}
}
```

### patch_json() - String Convenience

Apply a JSON patch string to a JSON document string:

```c++
auto result = glz::patch_json(
   R"({"a": 1})",
   R"([{"op": "add", "path": "/b", "value": 2}])"
);
if (result) {
   // *result is the JSON string: {"a":1,"b":2}
}
```

### diff() with Strings

Generate a patch from JSON strings:

```c++
auto patch = glz::diff(
   std::string_view{R"({"a": 1})"},
   std::string_view{R"({"a": 2})"}
);
```

## Options

### patch_opts

```c++
struct patch_opts {
   // If true, create intermediate objects for add operations (like mkdir -p)
   // Default: false (RFC 6902 compliant - parent must exist)
   bool create_intermediate = false;

   // If true, rollback all changes on any operation failure
   // Default: true (atomic application)
   // Note: Requires O(n) space and time for backup copy of large documents
   bool atomic = true;
};
```

Example with `create_intermediate`:

```c++
auto doc = glz::read_json<glz::generic>("{}");

glz::patch_opts opts{.create_intermediate = true};
glz::patch_document ops = {{glz::patch_op_type::add, "/a/b/c", glz::generic(42.0), std::nullopt}};

auto ec = glz::patch(*doc, ops, opts);
// doc is now {"a": {"b": {"c": 42}}}
```

### diff_opts

```c++
struct diff_opts {
   // Reserved for future implementation:
   bool detect_moves = false;   // Detect move operations
   bool detect_copies = false;  // Detect copy operations
   bool array_lcs = false;      // Use LCS for smarter array diffs
};
```

## Error Handling

`glz::patch()` returns an `error_ctx`. Common error codes:

| Error Code | Description |
|------------|-------------|
| `nonexistent_json_ptr` | Path does not exist (for remove/replace/test/move/copy) |
| `patch_test_failed` | Test operation value mismatch |
| `missing_key` | Required field (value/from) missing in patch operation |
| `syntax_error` | Invalid operation or move into self |
| `invalid_json_pointer` | Malformed JSON pointer syntax |

```c++
auto ec = glz::patch(*doc, ops);
if (ec) {
   if (ec.ec == glz::error_code::patch_test_failed) {
      // Test operation failed - values didn't match
   }
   else if (ec.ec == glz::error_code::nonexistent_json_ptr) {
      // Path doesn't exist
   }
}
```

## Round-trip Example

A common pattern is to diff two documents, serialize the patch, and apply it elsewhere:

```c++
// Generate patch
auto source = glz::read_json<glz::generic>(R"({"name": "Alice", "age": 30})");
auto target = glz::read_json<glz::generic>(R"({"name": "Alice", "age": 31, "city": "NYC"})");

auto patch = glz::diff(*source, *target);

// Serialize patch to JSON
auto patch_json = glz::write_json(*patch);
// [{"op":"replace","path":"/age","value":31},{"op":"add","path":"/city","value":"NYC"}]

// Later, deserialize and apply
auto ops = glz::read_json<glz::patch_document>(*patch_json);
auto doc = glz::read_json<glz::generic>(R"({"name": "Alice", "age": 30})");
glz::patch(*doc, *ops);
// doc now matches target
```

## JSON Serialization Format

Patch operations serialize to standard RFC 6902 format:

```json
[
   {"op": "add", "path": "/foo", "value": "bar"},
   {"op": "remove", "path": "/old"},
   {"op": "replace", "path": "/count", "value": 42},
   {"op": "move", "path": "/new", "from": "/old"},
   {"op": "copy", "path": "/backup", "from": "/data"},
   {"op": "test", "path": "/version", "value": 1}
]
```

## See Also

- [JSON Merge Patch (RFC 7386)](./json-merge-patch.md) - Simpler patching for partial updates
- [Generic JSON](./generic-json.md) - Working with `glz::generic`
- [JSON Pointer Syntax](./json-pointer-syntax.md) - Path syntax used by JSON Patch