When you need to update a JSON document, you could send the entire new document to the server. But for large documents where only a few values change, this wastes bandwidth and processing power. JSON Patch solves this by letting you send only the changes—a list of operations that describe exactly what to modify. This guide covers all six RFC 6902 operations with practical examples you can use immediately.
Key Takeaways
- 1JSON Patch reduces payload size by up to 95% for small changes to large documents
- 2The six operations (add, remove, replace, move, copy, test) cover all modification needs
- 3Operations are atomic—if one fails, none are applied
- 4JSON Pointer paths use / separators and ~1 for escaped slashes
- 5The test operation enables conditional updates and optimistic concurrency
Why Use JSON Patch?
Consider an API that manages user profiles. A typical profile might be 10KB of JSON with dozens of fields. If a user only changes their email address, sending the entire 10KB document is wasteful. With JSON Patch, you send just the change:
// Instead of sending the entire 10KB user object:
PATCH /api/users/123
Content-Type: application/json-patch+json
[
{ "op": "replace", "path": "/email", "value": "newemail@example.com" }
]This 70-byte patch replaces a 10KB payload—a 99% reduction in data transfer. Beyond bandwidth savings, JSON Patch provides:
- Atomicity — All operations succeed or none do, preventing partial updates
- Auditability — Patches serve as a change log, documenting exactly what was modified
- Conflict detection — The test operation enables optimistic concurrency control
- Standardization — RFC 6902 is widely supported across languages and frameworks
The Six Patch Operations
JSON Patch defines exactly six operations. Each operation is an object with an op field specifying the action and a path field specifying where to apply it. Some operations require additional fields like value or from.
| Operation | Purpose | Required Fields |
|---|---|---|
add | Insert a value at the target path | op, path, value |
remove | Delete the value at the target path | op, path |
replace | Replace the value at the target path | op, path, value |
move | Move a value from one path to another | op, from, path |
copy | Copy a value from one path to another | op, from, path |
test | Verify a value equals the expected value | op, path, value |
Add Operation
The add operation inserts a new value. For objects, it adds a new property. For arrays, it inserts at the specified index, shifting subsequent elements.
// Original document
{
"name": "Alice",
"roles": ["viewer"]
}
// Patch: Add email field and append "editor" role
[
{ "op": "add", "path": "/email", "value": "alice@example.com" },
{ "op": "add", "path": "/roles/-", "value": "editor" }
]
// Result
{
"name": "Alice",
"email": "alice@example.com",
"roles": ["viewer", "editor"]
}Note the special /- syntax for arrays—it means "append to the end." You can also use specific indices like /roles/0 to insert at the beginning.
Remove Operation
The remove operation deletes a value. The path must exist—removing a non-existent path causes an error.
// Original document
{
"name": "Alice",
"email": "alice@example.com",
"phone": "555-1234"
}
// Patch: Remove phone number
[
{ "op": "remove", "path": "/phone" }
]
// Result
{
"name": "Alice",
"email": "alice@example.com"
}Replace Operation
The replace operation updates an existing value. Unlike add, the path must already exist—replacing a non-existent path causes an error.
// Original document
{
"name": "Alice",
"status": "pending"
}
// Patch: Update status
[
{ "op": "replace", "path": "/status", "value": "approved" }
]
// Result
{
"name": "Alice",
"status": "approved"
}Move Operation
The move operation relocates a value from one path to another. It's equivalent to a remove followed by an add, but atomic.
// Original document
{
"user": {
"firstName": "Alice",
"lastName": "Smith"
}
}
// Patch: Move lastName to name.last (restructure)
[
{ "op": "move", "from": "/user/lastName", "path": "/user/last" }
]
// Result
{
"user": {
"firstName": "Alice",
"last": "Smith"
}
}Copy Operation
The copy operation duplicates a value from one path to another. The original value remains unchanged.
// Original document
{
"user": {
"email": "alice@example.com"
}
}
// Patch: Copy email to backup field
[
{ "op": "copy", "from": "/user/email", "path": "/user/backupEmail" }
]
// Result
{
"user": {
"email": "alice@example.com",
"backupEmail": "alice@example.com"
}
}Test Operation
The test operation verifies that a value equals the expected value. If the test fails, the entire patch is rejected. This enables optimistic concurrency control—ensuring you're updating the data you expect.
// Original document
{
"version": 5,
"data": "important stuff"
}
// Patch: Only update if version is still 5
[
{ "op": "test", "path": "/version", "value": 5 },
{ "op": "replace", "path": "/data", "value": "updated stuff" },
{ "op": "replace", "path": "/version", "value": 6 }
]
// If version changed since we read it, the entire patch failsThis pattern prevents lost updates when multiple clients modify the same document concurrently.
Understanding JSON Pointer Paths
JSON Patch uses JSON Pointer (RFC 6901) to specify locations in a document. Understanding the path syntax is essential for writing correct patches.
| Path | Targets | Notes |
|---|---|---|
"" | Document root | Empty string targets the whole document |
/name | Top-level name property | Single slash prefix |
/user/email | Nested email property | Each segment separated by / |
/items/0 | First array element | Zero-based index |
/items/- | End of array | Special syntax for append |
/a~1b | Property named a/b | ~1 escapes literal slash |
/a~0b | Property named a~b | ~0 escapes literal tilde |
// Document with tricky paths
{
"a/b": "slash in key",
"a~b": "tilde in key",
"items": [
{ "id": 1 },
{ "id": 2 }
]
}
// Patches targeting these paths:
{ "op": "replace", "path": "/a~1b", "value": "new value" } // targets "a/b"
{ "op": "replace", "path": "/a~0b", "value": "new value" } // targets "a~b"
{ "op": "replace", "path": "/items/1/id", "value": 999 } // targets items[1].idReal-World Examples
Updating User Profile Settings
// PATCH /api/users/123
[
{ "op": "replace", "path": "/settings/theme", "value": "dark" },
{ "op": "replace", "path": "/settings/notifications/email", "value": false },
{ "op": "add", "path": "/settings/notifications/push", "value": true }
]Manipulating Arrays
// Reorder items, remove one, add another
[
{ "op": "move", "from": "/items/2", "path": "/items/0" }, // Move item 2 to front
{ "op": "remove", "path": "/items/3" }, // Remove item at index 3
{ "op": "add", "path": "/items/-", "value": { "id": 99 }} // Append new item
]Conditional Update with Test
// Only approve if status is still "pending"
[
{ "op": "test", "path": "/status", "value": "pending" },
{ "op": "replace", "path": "/status", "value": "approved" },
{ "op": "add", "path": "/approvedAt", "value": "2026-02-03T10:30:00Z" },
{ "op": "add", "path": "/approvedBy", "value": "admin@example.com" }
]Implementing JSON Patch
Most programming languages have libraries for applying JSON Patch operations. Here's how to use them in common languages:
// JavaScript with fast-json-patch
import { applyPatch, compare } from 'fast-json-patch';
const document = { name: 'Alice', age: 30 };
const patch = [
{ op: 'replace', path: '/age', value: 31 },
{ op: 'add', path: '/email', value: 'alice@example.com' }
];
// Apply patch
const result = applyPatch(document, patch);
console.log(result.newDocument);
// { name: 'Alice', age: 31, email: 'alice@example.com' }
// Generate patch from two documents
const before = { name: 'Alice', age: 30 };
const after = { name: 'Alice', age: 31, email: 'alice@example.com' };
const generatedPatch = compare(before, after);
// Same patch as above# Python with jsonpatch
import jsonpatch
document = {"name": "Alice", "age": 30}
patch = jsonpatch.JsonPatch([
{"op": "replace", "path": "/age", "value": 31},
{"op": "add", "path": "/email", "value": "alice@example.com"}
])
# Apply patch
result = patch.apply(document)
print(result)
# {"name": "Alice", "age": 31, "email": "alice@example.com"}
# Generate patch from two documents
before = {"name": "Alice", "age": 30}
after = {"name": "Alice", "age": 31, "email": "alice@example.com"}
generated = jsonpatch.make_patch(before, after)
print(list(generated))Try JSON Patch in Our Tool
Our JSON Formatter tool includes a dedicated Patch panel where you can:
- Apply JSON Patch operations to documents interactively
- Generate patches by comparing two JSON documents
- Validate patch syntax before applying
- See step-by-step results of each operation