What is JSON Merge Patch?
JSON Merge Patch describes a change to a JSON document by sending a document that looks like the target: the members you include are set, the members you set to null are removed, and everything you leave out is untouched. It is the format most developers reach for by instinct when they write a PATCH handler, and RFC 7396 pins down the rules so client and server agree on them.
Content-Type: application/merge-patch+jsonRFC 7396 obsoletes RFC 7386, which defined the same format. Its alternative is JSON Patch, an explicit list of operations; PATCH Strategies compares the two.
1. The Merge Algorithm
The whole specification fits in a short recursive function. In outline:
merge(target, patch): if patch is not an object: return patch # scalars and arrays replace outright if target is not an object: target = {} # an object patch onto a non-object starts empty for each (name, value) in patch: if value is null: delete target[name] # no error if it was absent else: target[name] = merge(target[name], value) return targetRead off its consequences:
- Objects merge recursively. A nested object in the patch is merged into the matching nested object in the target.
nulldeletes. A member set tonullis removed from the target. Removing a member that does not exist is not an error.- Everything else replaces. Strings, numbers, booleans and arrays in the patch replace the target value outright.
- A non-object patch replaces the whole document. Sending
[]or"x"as the patch body is a full replacement, not a merge.
2. An Example
Given this resource:
{ "name": "Ada Lovelace", "contact": { "email": "ada@example.com", "phone": "+44 20 7946 0000" }, "roles": ["viewer", "editor"], "timezone": "Europe/London"}This request:
PATCH /v1/users/42 HTTP/1.1Host: api.example.comContent-Type: application/merge-patch+json
{ "name": "Ada King", "contact": { "phone": null }, "roles": ["viewer"], "locale": "en-GB"}Produces:
{ "name": "Ada King", "contact": { "email": "ada@example.com" }, "roles": ["viewer"], "timezone": "Europe/London", "locale": "en-GB"}name is replaced, locale is added, contact.phone is removed while contact.email survives, roles loses "editor" because the whole array was replaced, and timezone is untouched.
3. The Limits
Merge Patch buys its simplicity with three things it cannot express:
| You want to | Merge Patch | Why |
|---|---|---|
Set a member to null |
Not possible | null in a patch always means “remove” |
| Change one element of an array | Not possible | Arrays are replaced whole; send the full new array |
| Make the update conditional on a current value | Not possible | There is no equivalent of JSON Patch’s test; use If-Match |
The null limitation is the one that bites. If your API distinguishes “explicitly null” from “absent” (a middleName known to be empty versus one never collected), a Merge Patch client has no way to write the explicit null. Either model “no value” as absence throughout the API, or use JSON Patch for that resource.
Array replacement creates lost updates. Two clients that each append a tag by sending the array they last read will overwrite each other. Require an ETag and If-Match on any resource with arrays that clients edit, and return 412 Precondition Failed when it no longer matches.
4. Implementation Notes
- Every JSON value is a valid patch. RFC 7396 defines no errors of its own, so failures come from your own rules: a field the client may not change, or a result that fails validation. Return
422 Unprocessable Contentwith a problem details body naming the field, and415 Unsupported Media Typefor a body sent as plainapplication/jsonif you only accept the merge patch type. - Do not deserialize the patch into your resource model. Most languages cannot tell “field absent” from “field null” once the JSON is bound to a typed object, which turns every omitted field into a delete. Work on the parsed JSON tree, or use a type that preserves the three states.
- Validate after merging. Apply the patch to the current representation, then run the same schema validation you use for
PUT. - Advertise it.
Accept-Patch: application/merge-patch+jsontells clients which formats a resource takes; seeAccept-Patch.
Merge Patch is the right default for resources that are mostly flat objects of scalar fields. Reach for JSON Patch as soon as clients need to edit arrays element by element or set values to null.