When JSON Merge Patch cannot do the job
Both RFC 6902 and RFC 7386 describe a change to a JSON document, and the usual advice is that Merge Patch is simpler and smaller. It is — smaller in 96.2% of cases we measured. It also cannot express 9.6% of changes at all.
39,230 generated changes, both formats
| JSON Patch (RFC 6902) | Merge Patch (RFC 7386) | |
|---|---|---|
| Mean payload | 163 bytes | 107 bytes |
| Smaller of the two | 3.8% | 96.2% |
| Cannot express the change | 0% | 9.6% |
| Forced to resend a whole array | 0% | 21.0% |
39,230 randomly generated document changes. Each patch was generated and then applied, and the result compared against the intended target.
So the conventional advice is right about size and wrong about sufficiency. Merge Patch really is the more compact format almost every time. It is also unable to express one change in ten.
Merge Patch cannot set a value to null
RFC 7386 gives null a single meaning: remove this member. That leaves no way to say set this member to null.
before {"x":1,"y":2}
after {"x":null,"y":2} ← x set to null, not deleted
merge patch {"x":null} ← means DELETE x
json patch [{"op":"replace","path":"/x","value":null}]Apply that Merge Patch and x disappears. There is no error, and no flag to change the behaviour — the ambiguity is in the format itself.
If your API has nullable fields, this alone decides the question. Our patch generator detects the case and refuses to pretend, rather than emitting a patch that quietly deletes the field.
21.0% of changes resend an entire array
Merge Patch has no way to address an array element. Changing one item in a thousand-element array means sending all thousand.
before {"roles":["admin","dev"]}
after {"roles":["admin","ops","dev"]}
merge patch {"roles":["admin","ops","dev"]} ← the whole array
json patch [{"op":"replace","path":"/roles/1","value":"ops"},
{"op":"add","path":"/roles/2","value":"dev"}]On small arrays Merge Patch still wins on bytes, which is why it came out smaller overall. On large ones it inverts sharply, and it also destroys any chance of a meaningful concurrent merge: two clients editing different elements will clobber each other.
JSON Patch is atomic and can assert
RFC 6902 §5 requires that a patch is applied as a whole — if any operation fails, the document is left untouched. The test operation asserts a value before changing it:
[{"op":"test", "path":"/version", "value":7},
{"op":"replace","path":"/name", "value":"Ada Lovelace"}]If /version is no longer 7, nothing is applied. That is optimistic concurrency with no ETag, no lock and no extra round trip. Merge Patch has no equivalent — it always overwrites.
A short decision rule
| Use | When |
|---|---|
| Merge Patch (RFC 7386) | No nullable fields, arrays are small or replaced wholesale anyway, and you want the simplest possible client. Roughly half of public REST APIs land here. |
| JSON Patch (RFC 6902) | Nullable fields, large arrays, element-level edits, or you need atomicity and test-based concurrency control. |
One more consideration: Merge Patch is easy to hand-write and easy to read in a log. JSON Patch is neither. If humans will be reading your PATCH bodies during an incident, that counts for something.
Standards referenced
RFC 6902 and RFC 7386 are both IETF Standards Track documents.
- RFC 6902 §4 — Operations — JavaScript Object Notation (JSON) Patch, P. Bryan, Ed., M. Nottingham, Ed., 2013.
- RFC 6902 §5 — Error Handling — JavaScript Object Notation (JSON) Patch, P. Bryan, Ed., M. Nottingham, Ed., 2013.
- RFC 7386 — JSON Merge Patch, P. Hoffman, J. Snell, 2014.
- RFC 6901 §3 — Syntax — JavaScript Object Notation (JSON) Pointer, P. Bryan, Ed., K. Zyp, M. Nottingham, Ed., 2013.
- RFC 8259 (STD 90) — The JavaScript Object Notation (JSON) Data Interchange Format, T. Bray, Ed., 2017.
Only standards bodies and peer-reviewed venues are cited here.
How this was measured
Both generators were run over 39,230 randomly generated document changes; each patch was applied and the result compared to the intended target. The JSON Patch implementation passes the community 108/108 json-patch-tests conformance suite and reproduces the target on 30,000/30,000 random pairs. Every Merge Patch failure was predicted in advance by the limitation detector — none were surprises.
Every figure on this page came from executing the parser named, not from documentation. The collection harness is public — see the error registry method notes.