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
|