Skip to content

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

#include "glaze/json/patch.hpp"

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.

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.

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.

// 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.

// 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.

// 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:

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

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