JSON Patch (RFC 6902): Add, Remove, Replace, Move, Copy, and Test Operations
JSON Patch is a format for describing changes to a JSON document, defined in RFC 6902 (published April 2013). A patch is a JSON array of operation objects. Each operation has an "op" field specifying the operation type, a "path" field using JSON Pointer syntax (RFC 6901), and optional "value" or "from" fields depending on the operation. RFC 6902 defines 6 operation types: add, remove, replace, move, copy, and test. Operations are applied in sequence; if any operation fails, the entire patch is aborted and the document is left unchanged. JSON Patch is used with the HTTP PATCH method using Content-Type: application/json-patch+json. Before applying a patch, you can validate your JSON to ensure the document is well-formed, or use Jsonic's JSON Diff tool to visualize differences between two documents. New to JSON? Learn what JSON is first.
Need to see what changed between two JSON documents? Jsonic's JSON Diff tool generates a patch instantly.
Open JSON DiffJSON Patch structure
A patch is a JSON array. Each element is an operation object that must have at least two fields: "op" (the operation name) and "path" (the target location). Additional fields — "value" and "from" — are required or optional depending on the operation. Paths use JSON Pointer notation (RFC 6901): "/" is the root document itself, "/name" targets the top-level name key, "/address/city" targets a nested key. For arrays, numeric tokens like "/tags/0" target the element at index 0. The special token "-" in an array path refers to the position after the last element, used exclusively with add to append. The example below renames a field using move and changes a value using replace.
// Original document
{ "title": "Hello", "body": "World", "draft": true }
// Patch: rename "title" -> "subject", set draft = false
[
{ "op": "move", "from": "/title", "path": "/subject" },
{ "op": "replace", "path": "/draft", "value": false }
]
// Result
{ "body": "World", "draft": false, "subject": "Hello" }Operations are applied strictly in order. If the move fails (e.g., /title does not exist), the replace never runs and the document is returned unchanged. This atomic, all-or-nothing behaviour makes JSON Patch safe for concurrent update scenarios. For a deeper look at comparing JSON documents and generating patches automatically, see the JSON diff tutorial.
The add operation
The add operation inserts a value at the specified path. On an object, if the key already exists it replaces the existing value; if the key is absent it creates it. On an array, add inserts before the element at the given index, shifting subsequent elements right. Using the special index "-" appends to the end of the array — this is the idiomatic way to push a new item without knowing the current length. The "value" field is required foradd. The example below shows adding a new top-level field and appending an element to an existing array.
// Original
{ "name": "Widget", "tags": ["sale"] }
// Patch
[
{ "op": "add", "path": "/price", "value": 9.99 },
{ "op": "add", "path": "/tags/-", "value": "new" }
]
// Result
{ "name": "Widget", "tags": ["sale", "new"], "price": 9.99 }To insert at a specific position in an array rather than appending, use a numeric index: {"op": "add", "path": "/tags/0", "value": "featured"} inserts "featured" at position 0, pushing all existing elements forward. Inserting at an index greater than the current array length is an error. The add operation is the only one that accepts "-" as an array index.
The remove operation
The remove operation deletes the value at the given path. It requires only the "op" and "path" fields — no "value". If the path does not exist, the operation fails and the entire patch is aborted. When removing an element from an array, the remaining elements shift down to fill the gap (no sparse arrays are created). Removing a key from an object simply eliminates that key-value pair. The example below removes a top-level field and an element at a specific array index.
// Original
{ "name": "Widget", "price": 9.99, "tags": ["sale", "new", "draft"] }
// Patch: remove price field, remove "draft" tag (index 2)
[
{ "op": "remove", "path": "/price" },
{ "op": "remove", "path": "/tags/2" }
]
// Result
{ "name": "Widget", "tags": ["sale", "new"] }Because array indices shift after each removal, order matters when removing multiple elements from the same array. Remove from the highest index first to avoid off-by-one errors, or recalculate indices between operations. Alternatively, use the test operation beforehand to guard against stale index assumptions.
The replace operation
The replace operation is semantically equivalent to a remove followed by an add at the same path. The critical difference from add is that replace fails if the target path does not exist — it will never create a new key. This makes it the right choice when you want to enforce that a field must already be present, preventing accidental creation of fields with the wrong name. The "value" field is required. The replacement value can be any valid JSON type including objects, arrays, numbers, strings, booleans, and null.
// Original
{ "status": "draft", "version": 2, "content": "Hello" }
// Patch
[
{ "op": "replace", "path": "/status", "value": "published" },
{ "op": "replace", "path": "/version", "value": 3 }
]
// Result
{ "status": "published", "version": 3, "content": "Hello" }Attempting to replace a path that doesn't exist — for example "/nonexistent" — will fail the entire patch. If you want upsert behavior (create if absent, update if present), use add instead.
The move operation
The move operation removes a value from the "from" path and inserts it at the "path" destination. It is equivalent to a remove from "from" followed by an add to "path". Both "from" and "path" are required. The most common use case is renaming a JSON key: since JSON objects do not have a native rename operation, move is the idiomatic JSON Patch approach. Moving a value within an array reorders the array. The "from" path must exist; "path" follows the same rules as add (it may or may not exist).
// Original
{ "firstName": "Alice", "lastName": "Smith", "age": 30 }
// Patch: rename firstName -> first_name, lastName -> last_name
[
{ "op": "move", "from": "/firstName", "path": "/first_name" },
{ "op": "move", "from": "/lastName", "path": "/last_name" }
]
// Result
{ "age": 30, "first_name": "Alice", "last_name": "Smith" }A move where "from" and "path" are identical is a no-op and always succeeds. You cannot move a value to a path that is a child of its current location (e.g., moving /a to /a/b is prohibited by the spec). For more strategies to compare and transform JSON structures, see the guide on compare two JSON files.
The copy operation
The copy operation copies the value at the "from" path to the "path" destination without removing the original. It is equivalent to an add at "path" using the value currently at "from". Both "from" and "path" are required;"value" is not used. The "from" path must exist. The copied value is a deep copy — modifying the destination after the patch does not affect the source. This is useful for duplicating nested objects, copying array elements, or populating a computed field from an existing one.
// Original
{
"billing": { "street": "123 Main St", "city": "Austin", "zip": "78701" },
"shipping": {}
}
// Patch: copy billing address to shipping
[
{ "op": "copy", "from": "/billing", "path": "/shipping" }
]
// Result
{
"billing": { "street": "123 Main St", "city": "Austin", "zip": "78701" },
"shipping": { "street": "123 Main St", "city": "Austin", "zip": "78701" }
}Unlike move, copy does not remove the source value, so both paths exist in the resulting document. You can copy a value to a path that already exists — it will be replaced, following the same rules as add.
The test operation
The test operation asserts that the value at "path" is equal to "value". If the assertion fails — meaning the actual value does not match — the entire patch is aborted immediately and the document is left unchanged. No subsequent operations in the patch array are applied. This makes test a precondition guard. The primary use case is optimistic concurrency control: prepend a test on a version field to ensure the document has not been modified by another process since you last read it. If the version has changed, the patch fails atomically rather than applying stale updates.
// Only update if version is still 3 (optimistic lock)
[
{ "op": "test", "path": "/version", "value": 3 },
{ "op": "replace", "path": "/status", "value": "published" },
{ "op": "replace", "path": "/version", "value": 4 }
]
// If /version !== 3, the entire patch is rejected.
// If /version === 3, status and version are updated atomically.The equality check uses JSON equality: two objects are equal if they have the same keys and values (key order does not matter); two arrays are equal if they have the same elements in the same order. test can also assert that a path does not exist by checking that it equals null, though libraries handle this differently — check your implementation's behaviour for missing-path tests.
Apply JSON Patch in JavaScript
The most popular JavaScript library is fast-json-patch (npm, 2M+ weekly downloads). It is available for both Node.js and browser environments, supports the full RFC 6902 operation set, handles ~0/~1 escaping correctly, and performs atomic test-failure aborts. Install it with npm install fast-json-patch. The primary function applyPatch(document, patch) returns an object with a newDocument property (the patched result) and does not mutate the original document by default. Additional utilities include compare(doc1, doc2) to generate a patch between two documents (useful for building a diff — also see JSON diff tutorial) and validate(patch) to check patch syntax before applying.
import jsonpatch from 'fast-json-patch'
const doc = { name: 'Alice', age: 30, roles: ['user'] }
const patch = [
{ op: 'replace', path: '/name', value: 'Bob' },
{ op: 'add', path: '/roles/-', value: 'admin' },
{ op: 'remove', path: '/age' },
]
const result = jsonpatch.applyPatch(doc, patch).newDocument
// { name: 'Bob', roles: ['user', 'admin'] }
// Generate a patch from two documents
const original = { name: 'Alice', age: 30 }
const updated = { name: 'Bob', age: 30 }
const diff = jsonpatch.compare(original, updated)
// [{ op: 'replace', path: '/name', value: 'Bob' }]To mutate the document in place (faster for large documents when you don't need the original), pass true as the third argument to applyPatch. For single operations, use applyOperation(doc, operation). For TypeScript, fast-json-patch ships its own type definitions — no@types package needed.
Apply JSON Patch in Python
The standard Python library is jsonpatch. Install it with pip install jsonpatch. It supports all 6 RFC 6902 operations and integrates naturally with Python dicts. The main entry point is jsonpatch.JsonPatch(operations), which accepts a list of operation dicts. Call patch.apply(doc) to return a new dict with the patch applied (the original is not mutated). You can also use the convenience function jsonpatch.apply_patch(doc, patch_list) for a one-liner. To generate a patch from two dicts, use jsonpatch.make_patch(original, updated) — the result is a JsonPatch object that you can serialize with str(patch) or patch.to_string().
import jsonpatch
doc = {"name": "Alice", "age": 30, "roles": ["user"]}
patch = jsonpatch.JsonPatch([
{"op": "replace", "path": "/name", "value": "Bob"},
{"op": "add", "path": "/roles/-", "value": "admin"},
{"op": "remove", "path": "/age"},
])
result = patch.apply(doc)
# {'name': 'Bob', 'roles': ['user', 'admin']}
# Generate a patch between two dicts
original = {"name": "Alice", "age": 30}
updated = {"name": "Bob", "age": 30}
diff = jsonpatch.make_patch(original, updated)
print(diff)
# [{"op": "replace", "path": "/name", "value": "Bob"}]If a test operation fails, jsonpatch raises a JsonPatchTestFailed exception. Wrap patch.apply() in a try/except block to handle test failures gracefully in production code. The library is pure Python with no C extensions, making it easy to install in restricted environments.
JSON Patch vs JSON Merge Patch
RFC 6902 (JSON Patch) and RFC 7396 (JSON Merge Patch) are two separate IETF standards for describing updates to JSON documents. JSON Merge Patch is simpler: the patch is itself a partial JSON document. Keys present in the patch overwrite the corresponding keys in the target; a null value means delete the key; absent keys are left unchanged. JSON Patch is more expressive, supporting move, copy, and test, and enabling precise array manipulation by index. The table below summarises the key differences.
| JSON Patch (RFC 6902) | JSON Merge Patch (RFC 7396) | |
|---|---|---|
| Format | Array of operation objects | Partial JSON document |
| Delete a key | remove operation | Set key to null |
| Set key to null | replace with null | Not possible (null = delete) |
| Move / rename | Yes (move) | No |
| Copy | Yes (copy) | No |
| Conditional update | Yes (test) | No |
| Array insert by index | Yes | No (replaces entire array) |
| Content-Type | application/json-patch+json | application/merge-patch+json |
When to use JSON Patch: precise array operations (insert at index, append, remove by index), rename a key, conditional updates with test, or complex multi-step transformations where atomicity matters. When to use JSON Merge Patch: simple key-value updates where null semantically means "delete this key" and you don't need array precision or move/copy/test. For building and inspecting patches visually, try Jsonic's JSON Diff or read the compare two JSON files guide.
Frequently asked questions
What is JSON Patch?
JSON Patch is a structured format for expressing a sequence of changes to apply to a JSON document. It is defined in RFC 6902 and published by the IETF in April 2013. A JSON Patch document is itself a JSON array of operation objects. Each object specifies an operation ("op"), a target location ("path" using JSON Pointer syntax from RFC 6901), and optionally a value or source path. Operations are applied in order. If any operation fails — for example, trying to remove a path that doesn't exist, or a test assertion that doesn't match — the entire patch is rejected and the document remains unchanged. JSON Patch is commonly used in HTTP PATCH requests with Content-Type: application/json-patch+json. It is especially useful for API clients that need to make precise, incremental updates to server-side resources without sending the entire updated document.
What are the 6 JSON Patch operations?
RFC 6902 defines exactly 6 operations: (1) add — inserts a value at the specified path; on arrays, can append with "-". (2) remove — deletes the value at the path; fails if path does not exist. (3) replace — replaces an existing value; fails if path does not exist (unlike add). (4) move — removes value from "from" path and adds it to "path". (5) copy — copies value from "from" path to "path" without removing it. (6) test — asserts that the value at "path" equals "value"; if the assertion fails, the entire patch is aborted. The test operation is critical for conditional updates: you can prepend {"op": "test", "path": "/version", "value": 5} to a patch to ensure the document has not changed since you read it, implementing optimistic concurrency control.
What is JSON Pointer and how is it used in JSON Patch?
JSON Pointer (RFC 6901) is the path syntax used in JSON Patch "path" and "from" fields. A JSON Pointer is a string of zero or more tokens, each preceded by a /. The empty string "" refers to the entire document. /name refers to the name key of the root object. /address/city refers to doc.address.city. /tags/0 refers to the first element of the tags array. The special token - in an array path refers to the non-existent element after the last element — used exclusively with add to append: {"op": "add", "path": "/tags/-", "value": "new"}. Keys containing / are escaped as ~1; keys containing ~ are escaped as ~0. So a key literally named a/b is referenced as /a~1b.
How is JSON Patch different from JSON Merge Patch?
JSON Patch (RFC 6902) and JSON Merge Patch (RFC 7396) are two separate IETF standards for patching JSON documents. JSON Merge Patch is simpler: the patch is itself a partial JSON document where present keys are updated, null values delete keys, and absent keys are left unchanged. It is intuitive for simple key-value updates but cannot express precise array operations or moves. JSON Patch is more expressive: it supports move, copy, and test operations, and can insert or remove specific array elements by index. JSON Merge Patch uses Content-Type application/merge-patch+json; JSON Patch uses application/json-patch+json. Choose JSON Merge Patch for simple field updates (e.g., "set the name field"). Choose JSON Patch when you need to append to an array, rename a key (move), or apply conditional updates (test).
How do I apply JSON Patch in JavaScript?
The most popular library is fast-json-patch (npm, 2M+ weekly downloads). Install: npm install fast-json-patch. The main function is applyPatch(document, patch), which returns an object with newDocument and the applied operations. It does NOT mutate the original document by default. For mutations, pass true as the third argument or use applyOperation() for single operations. The library also provides compare(doc1, doc2) to generate a patch from two documents, and validate(patch) to check patch syntax before applying. For browser use, it works without a bundler as an ES module. The jsonpatch package is an older alternative with a similar API. For Node.js on the server, fast-json-patch is the production standard: it handles edge cases like ~0/~1 escaping, atomic test failures, and array bounds checking correctly.
Can JSON Patch be reversed (undone)?
JSON Patch is not inherently reversible, but you can generate a reverse patch with tools. The fast-json-patch library provides compare(newDoc, originalDoc) — note the argument order — to generate the patch that transforms the patched document back to the original. This reverse patch can then be stored alongside the forward patch to implement undo functionality. For a more robust undo system, store the original document snapshot before applying each patch, rather than relying on computed reverse patches — inverse computation can fail for operations like remove if the removed value was not captured. The test operation can also be used as a guard: prepend {"op":"test","path":"/version","value":N} so that replaying the same patch twice fails on the second attempt, preventing accidental double-application.
Work with JSON Patch on Jsonic
Use Jsonic's JSON Diff tool to generate a JSON Patch from two documents, or validate your JSON before applying a patch. You can also read the JSON diff tutorial for more on comparing JSON documents programmatically.
Open JSON Diff