JSON Patch (RFC 6902)¶
Glaze provides full support for JSON Patch (RFC 6902), a format for describing changes to JSON documents. JSON Patch works with glz::generic for runtime JSON manipulation.
See also: JSON Merge Patch (RFC 7386) for a simpler alternative when you don't need fine-grained control over array elements or explicit null values.
Include¶
Basic Usage¶
Applying a Patch¶
Use glz::patch() to apply a patch document to a JSON value:
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:
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.
// 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.
replace¶
Replaces the value at the specified path. The path must exist.
move¶
Moves a value from one location to another. Equivalent to remove + add.
copy¶
Copies a value from one location to another.
test¶
Tests that the value at the specified path equals the given value. If the test fails, the entire patch operation fails.
// 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:
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:
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:
Options¶
patch_opts¶
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:
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¶
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 |
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:
// 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:
[
{"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) - Simpler patching for partial updates
- Generic JSON - Working with
glz::generic - JSON Pointer Syntax - Path syntax used by JSON Patch