Definition
Imagine you’re a surgeon operating on a patient. You don’t describe the desired end state (“I want a healthy patient”). Instead, you give precise, sequential instructions: “Make incision here, clamp this vessel, remove this tissue, close with sutures.” Each instruction is atomic, explicit, and sequenced. JSON Patch works the same way for data - instead of describing what you want the final result to look like, you describe the exact operations to perform: add this field, remove that one, replace this value, move this over there.
JSON Patch (defined in RFC 6902) is a format for expressing a sequence of operations to apply to a JSON document. Unlike JSON Merge Patch which shows what the data should become, JSON Patch specifies exactly what to do: six operation types (add, remove, replace, move, copy, test) with precise paths using JSON Pointer notation. Each operation in the array executes in sequence. If any operation fails, the entire patch should be rejected (atomic behavior). This explicit, surgical approach gives you complete control over complex updates.
The power of JSON Patch shines in complex scenarios. Need to add an element to the middle of an array? {"op": "add", "path": "/items/2", "value": "new item"}. Move a field from one location to another? {"op": "move", "from": "/old/path", "path": "/new/path"}. Ensure a value equals something before changing it (optimistic concurrency)? {"op": "test", "path": "/version", "value": 5} - if the test fails, the patch fails. You can express transformations that JSON Merge Patch simply cannot, making it the tool of choice for sophisticated data manipulation.
Example
Real-World Scenario 1: Array Manipulation
You have a todo list API where order matters. Users can insert, remove, or reorder items. With JSON Merge Patch, you’d have to send the entire array. With JSON Patch: [{"op": "add", "path": "/todos/2", "value": {"text": "New task", "done": false}}] inserts at position 2, shifting others down. Reorder with {"op": "move", "from": "/todos/5", "path": "/todos/1"}. Precise array surgery.
Real-World Scenario 2: Optimistic Concurrency Control
Two users edit the same document. User A’s patch includes a test: [{"op": "test", "path": "/version", "value": 15}, {"op": "replace", "path": "/title", "value": "New Title"}, {"op": "replace", "path": "/version", "value": 16}]. If User B already updated the version to 16, User A’s patch fails at the test operation, preventing a conflict. The server rejects the entire patch atomically.
Real-World Scenario 3: Complex Object Restructuring
An API needs to reorganize user data during a migration. A single patch can move fields, copy values, and update paths: [{"op": "copy", "from": "/address/city", "path": "/location/city"}, {"op": "move", "from": "/phone", "path": "/contact/phone"}, {"op": "remove", "path": "/address"}, {"op": "add", "path": "/migrated", "value": true}]. One atomic operation handles the entire restructure.
Real-World Scenario 4: Audit Trail of Changes Because JSON Patch explicitly lists every operation, it can serve as an audit log. You can store the patches themselves: “User X applied these exact operations at this time.” This is much more informative than storing snapshots, and you can replay patches to recreate any historical state.
Analogy
The Surgical Operation: JSON Patch is like surgical instructions. You don’t say “make the patient healthy” - you give step-by-step procedures: “incise here, clamp this, suture that.” Each step is explicit, ordered, and precise. JSON Merge Patch is describing the desired outcome; JSON Patch is the surgery itself.
The Git Diff: JSON Patch is like a git diff that shows exact changes line by line: add this line here, remove that line there, move this block. The diff doesn’t show the final file - it shows the operations to transform one version into another. You can apply the diff forward or reverse it.
The Recipe vs. Photo: JSON Merge Patch is like showing a photo of the finished dish and saying “make it look like this.” JSON Patch is the actual recipe: “Add 2 cups flour, mix for 3 minutes, bake at 350°F.” The recipe tells you exactly what to do, in order, to get the result.
The Dance Choreography: JSON Merge Patch describes where dancers should end up. JSON Patch is the choreography: “Step left, turn, step right, arms up.” Each move is explicit and sequenced. Following the choreography guarantees you reach the intended position.
Code Example
// JSON Patch request
// PATCH /users/123
// Content-Type: application/json-patch+json
const patch = [
// Replace a value
{ "op": "replace", "path": "/email", "value": "[email protected]" },
// Remove a field
{ "op": "remove", "path": "/phone" },
// Add a new field
{ "op": "add", "path": "/verified", "value": true },
// Test before proceeding (optimistic locking)
{ "op": "test", "path": "/version", "value": 3 },
// Move a field to new location
{ "op": "move", "from": "/oldAddress", "path": "/address" },
// Copy a value
{ "op": "copy", "from": "/email", "path": "/backupEmail" },
// Array operations
{ "op": "add", "path": "/tags/0", "value": "priority" }, // Insert at beginning
{ "op": "add", "path": "/tags/-", "value": "new" }, // Append to end
{ "op": "remove", "path": "/tags/2" }, // Remove at index 2
{ "op": "replace", "path": "/items/0/quantity", "value": 5 } // Nested array object
];
// Operations explained:
// - add: Insert at path (appends if path ends with array index or -)
// - remove: Delete at path
// - replace: Update value at path (path must exist)
// - test: Assert value equals expected (fails patch if not)
// - move: Remove from 'from' and add at 'path'
// - copy: Copy from 'from' to 'path' (original remains)
// Using fast-json-patch library
const jsonpatch = require('fast-json-patch');
const original = {
"name": "Alice",
"email": "[email protected]",
"version": 3,
"tags": ["user", "active"]
};
const operations = [
{ "op": "replace", "path": "/email", "value": "[email protected]" },
{ "op": "add", "path": "/tags/-", "value": "updated" }
];
// Apply patch
const result = jsonpatch.applyPatch(original, operations).newDocument;
// result.email = "[email protected]"
// result.tags = ["user", "active", "updated"]
// Generate patch by diffing two objects
const before = { "name": "Alice", "age": 30 };
const after = { "name": "Alice", "age": 31, "verified": true };
const diff = jsonpatch.compare(before, after);
// diff = [
// { "op": "replace", "path": "/age", "value": 31 },
// { "op": "add", "path": "/verified", "value": true }
// ]
// API endpoint example (Express.js)
app.patch('/users/:id', async (req, res) => {
const contentType = req.get('Content-Type');
if (contentType === 'application/json-patch+json') {
const original = await getUserById(req.params.id);
try {
const result = jsonpatch.applyPatch(
original,
req.body,
true, // Validate operations
true // Mutate original
);
await saveUser(req.params.id, result.newDocument);
res.json(result.newDocument);
} catch (error) {
// Patch failed (test failed, invalid operation, etc.)
res.status(409).json({
error: 'Patch operation failed',
message: error.message
});
}
}
});