Formatter Validator Minifier Escape / Unescape JSON ↔ String JSON ↔ YAML JSON → CSV NDJSON / JSON Lines JSON → Types JSON ↔ XML JSON Diff JSON Patch Canonicalize (RFC 8785) Schema Validator Schema Generator JSONPath Tester JWT Decoder JSON Errors Schemas More Tools
JSONTools / Blog / Measured
Measured

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 payload163 bytes107 bytes
Smaller of the two3.8%96.2%
Cannot express the change0%9.6%
Forced to resend a whole array0%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

UseWhen
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.

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.

More on this