When an HTTP API needs to update part of a resource, two IETF standards compete for the job: JSON Merge Patch (RFC 7396) and JSON Patch (RFC 6902). They look similar but work very differently — Merge Patch overlays a partial object, while JSON Patch sends an explicit list of operations. This guide shows how each works, compares them head to head, and helps you pick the right one.
The Problem: Partial Updates
A PUT request replaces an entire resource, which is wasteful and risky when you only want to change one field. The HTTP PATCH method exists for partial updates — but PATCH doesn't define a body format. That's what these two standards provide.
JSON Merge Patch (RFC 7396)
A Merge Patch is just a partial version of the target document. The server overlays it: keys in the patch are set, keys set to null are deleted, and keys not mentioned are left alone.
// Original
{ "title": "Hello", "author": "Ada", "tags": ["a", "b"], "draft": true }
// Merge Patch body (Content-Type: application/merge-patch+json)
{ "title": "Hello World", "draft": null }
// Result
{ "title": "Hello World", "author": "Ada", "tags": ["a", "b"] }
// ^ title updated ^ author untouched ^ draft deleted (was null) It's intuitive and compact. The catch: because null means "delete," you cannot use Merge Patch to set a value to null, and arrays can only be replaced wholesale — there's no way to change a single array element.
JSON Patch (RFC 6902)
A JSON Patch is an ordered array of operations, each targeting a location with a JSON Pointer path. It's explicit and precise:
// JSON Patch body (Content-Type: application/json-patch+json)
[
{ "op": "replace", "path": "/title", "value": "Hello World" },
{ "op": "remove", "path": "/draft" },
{ "op": "add", "path": "/tags/-", "value": "c" },
{ "op": "test", "path": "/author", "value": "Ada" }
]The six operations:
| op | Effect |
|---|---|
add | Add a value (or append to an array with /-) |
remove | Delete the value at a path |
replace | Replace the value at a path |
move | Move a value from one path to another |
copy | Copy a value to another path |
test | Assert a value (abort the whole patch if it fails) |
The test op enables safe, atomic updates: if the document isn't in the expected state, the entire patch is rejected — useful for optimistic concurrency control.
Head-to-Head Comparison
| JSON Merge Patch | JSON Patch | |
|---|---|---|
| RFC | 7396 | 6902 |
| Content-Type | application/merge-patch+json | application/json-patch+json |
| Readability | High — looks like the data | Lower — list of operations |
| Can set a value to null | No (null means delete) | Yes |
| Modify a single array element | No (replace whole array) | Yes (by index) |
| Conditional / atomic updates | No | Yes (test op) |
| Move / copy | No | Yes |
| Best for | Simple object field updates | Precise, array-aware, atomic edits |
When to Use Which
- JSON Merge Patch — when clients mostly update top-level object fields and you value a body that reads like the resource. Great for simple settings and profile updates. Just remember you can't set fields to
nullor edit array items. - JSON Patch — when you need to edit array elements, move or copy values, set fields to
null, or apply changes atomically with a precondition. The standard choice for collaborative editing and audit-friendly change sets.
HTTP PATCH Semantics: Idempotency and Headers
Two HTTP details that often catch teams shipping a PATCH endpoint:
- PATCH is not required to be idempotent (unlike PUT). A JSON Patch with
{"op":"add","path":"/items/-","value":...}appends — applying it twice adds twice. If you want safe retries, either use atestop to assert state first, or design your ops so re-application is a no-op (usereplaceon a fixed path instead ofaddto/items/-). - Use the right Content-Type. RFC 6902 =
application/json-patch+json; RFC 7396 =application/merge-patch+json. Servers usually branch on this. - Optimistic concurrency. Combine a JSON Patch
testop (or anIf-MatchETag header) so concurrent writers can't silently overwrite each other.
Libraries Worth Knowing
For JSON Patch, fast-json-patch is the de-facto npm package — it applies, generates (diff two documents to produce a patch), and observes (track changes to a watched object). For JSON Merge Patch, the rules are tiny enough to inline, but json-merge-patch implements them with the null-deletion edge cases handled.
Applying Patches in Code
// JavaScript — fast-json-patch implements RFC 6902
import { applyPatch } from 'fast-json-patch';
const updated = applyPatch(document, patchOps).newDocument;
// JSON Merge Patch is trivial to apply by recursion, but a library
// (e.g. json-merge-patch) handles the null-deletion rules correctly.After applying a patch, you can confirm the change is exactly what you intended by diffing the before and after — see How to Compare Two JSON Files or paste both into JSON Diff.
Frequently Asked Questions
What's the difference between JSON Patch and JSON Merge Patch?
JSON Merge Patch (RFC 7396) is a partial copy of the document where null deletes a key. JSON Patch (RFC 6902) is an explicit array of operations (add, remove, replace, move, copy, test) targeting JSON Pointer paths. Merge Patch is simpler; JSON Patch is more powerful.
Why can't JSON Merge Patch set a field to null?
Because in Merge Patch null is the signal to delete a key. If you need to store an actual null value, use JSON Patch with a replace op.
Which Content-Type should I use?
application/merge-patch+json for Merge Patch and application/json-patch+json for JSON Patch, both sent with the HTTP PATCH method.
How do I edit one element of an array?
Use JSON Patch with a path like /items/2, or append with /items/-. Merge Patch can only replace the entire array.
Related Tools & Reading
- JSON Diff — verify a patch produced exactly the change you expected
- How to Compare Two JSON Files — the algorithms behind a semantic diff
- RFC 7396: JSON Merge Patch
- RFC 6901 & 6902: JSON Pointer and Patch