JSON Merge Patch (RFC 7396): Partial Updates with null as Delete

JSON Merge Patch is a format for partially updating a JSON document, defined in RFC 7396 (October 2014). The patch itself is a JSON object where: present keys update the corresponding keys in the target, null values delete the key from the target, and absent keys are left unchanged. This makes it far simpler than JSON Patch (RFC 6902) for common update operations: to change a user's name, send {"name": "Bob"}; to delete their phone number, send {"phone": null}. JSON Merge Patch is used with HTTP PATCH requests and the Content-Type application/merge-patch+json. The algorithm recursively merges nested objects but replaces arrays entirely — there is no way to append a single array element. Before sending a patch, use Jsonic to validate your JSON and catch syntax errors early.

Need to diff two JSON documents to generate a patch? Jsonic's JSON Diff tool computes the delta instantly.

Open JSON Diff

JSON Merge Patch algorithm (RFC 7396, Section 2)

The merge rule is simple and recursive. Given a target document and a patch object, the algorithm processes each key in the patch:

// Pseudocode — RFC 7396 Section 2
define MergePatch(target, patch):
  if patch is not an object:
    return patch                        // replace target entirely
  if target is not an object:
    target = {}                         // start from empty object
  for each (key, value) in patch:
    if value is null:
      remove key from target            // null = delete
    else:
      target[key] = MergePatch(target[key], value)  // recurse or replace
  return target

Three rules cover every case: (1) if the patch value is null, delete that key from the target; (2) if the patch value is an object and the target value at that key is also an object, recursively merge them; (3) otherwise, replace the target value with the patch value. Keys absent from the patch are never touched.

Here is a complete before/after example showing all three rules in one patch:

// Original document
{
  "name": "Alice",
  "age": 30,
  "phone": "+1-555-1234",
  "address": { "city": "Austin", "zip": "78701" }
}

// Patch
{
  "name": "Bob",
  "phone": null,
  "address": { "city": "Dallas" }
}

// Result
{
  "name": "Bob",
  "age": 30,
  "address": { "city": "Dallas", "zip": "78701" }
}

Breaking down the result: name is updated to "Bob" (present in patch), age is unchanged at 30 (absent from patch), phone is deleted (null in patch), address.city is updated to "Dallas" (nested merge), and address.zip is preserved as "78701" (absent from the nested patch object). To compare two JSON documents and see their differences visually, use Jsonic's diff tool.

Apply JSON Merge Patch in JavaScript

No external library is needed for the basic algorithm. The implementation below follows RFC 7396 exactly in about 10 lines. It is non-mutating — it creates a new object rather than modifying the target in place, which avoids surprising side effects when the same target object is referenced elsewhere in your application.

function mergePatch(target, patch) {
  if (typeof patch !== 'object' || patch === null) return patch
  const result = typeof target === 'object' && target !== null
    ? { ...target }
    : {}
  for (const [key, value] of Object.entries(patch)) {
    if (value === null) {
      delete result[key]
    } else {
      result[key] = mergePatch(result[key], value)
    }
  }
  return result
}

// Usage
const original = { name: 'Alice', age: 30, phone: '+1-555-1234' }
const patch = { name: 'Bob', phone: null }
const updated = mergePatch(original, patch)
// { name: 'Bob', age: 30 }

For npm, install the json-merge-patch package (MIT license, zero dependencies):

npm install json-merge-patch
import mergePatch from 'json-merge-patch'

// Apply a patch
const result = mergePatch.apply(target, patch)

// Generate a patch from two documents (compute the delta)
const patch = mergePatch.generate(original, updated)

The mergePatch.generate(original, target) function is particularly useful: given a before and after snapshot of a document, it produces the minimal merge patch that transforms one into the other. This is the correct way to compute what to send in a PATCH request when you have a full updated document. To understand what is JSON and how JSON values are structured, see the foundational guide.

Apply JSON Merge Patch in Python

The standard library is sufficient for a correct RFC 7396 implementation. The function below uses copy.deepcopy to avoid mutating the input target, which is important when the same dict is used in multiple places. The logic mirrors the JavaScript implementation: iterate over patch keys, delete on None, recurse on nested dicts, replace otherwise.

import copy

def merge_patch(target, patch):
    if not isinstance(patch, dict):
        return patch
    result = copy.deepcopy(target) if isinstance(target, dict) else {}
    for key, value in patch.items():
        if value is None:
            result.pop(key, None)
        else:
            result[key] = merge_patch(result.get(key), value)
    return result

# Usage
original = {"name": "Alice", "age": 30, "phone": "+1-555-1234"}
patch = {"name": "Bob", "phone": None}
updated = merge_patch(original, patch)
# {"name": "Bob", "age": 30}

Note that Python's None serializes to JSON null via the json module, so a patch received over HTTP (parsed with json.loads()) will correctly map JSON null to Python None before being passed to merge_patch().

For more powerful merge strategies (per-key merge rules, array union, etc.), install jsonmerge via pip:

pip install jsonmerge
from jsonmerge import merge

result = merge(base, head)  # RFC 7396 behaviour for plain dicts

jsonmerge supports merge strategy annotations via JSON Schema, which lets you declare per-field rules such as array append, replace, or union — useful when the default RFC 7396 array-replacement behaviour is too blunt for your data model.

HTTP PATCH with JSON Merge Patch

The correct Content-Type for a JSON Merge Patch request is application/merge-patch+json (not application/json). Using the correct MIME type lets servers route to a merge-patch handler distinct from a full-replace handler and lets API clients be explicit about the semantics of the request. Express.js example:

import express from 'express'

const app = express()

app.patch(
  '/users/:id',
  express.json({ type: 'application/merge-patch+json' }),
  async (req, res) => {
    const current = await db.users.findById(req.params.id)
    if (!current) return res.status(404).json({ error: 'Not found' })
    const updated = mergePatch(current, req.body)
    await db.users.save(updated)
    res.json(updated)
  }
)

The express.json({ type: 'application/merge-patch+json' }) option tells Express to parse the body only when the Content-Type matches, preventing merge-patch handlers from accidentally processing unrelated JSON payloads. Client-side fetch:

await fetch('/users/1', {
  method: 'PATCH',
  headers: { 'Content-Type': 'application/merge-patch+json' },
  body: JSON.stringify({ name: 'Bob', phone: null }),
})

Most REST clients (Postman, Insomnia, curl) accept application/json as well, but application/merge-patch+json is the correct MIME type per RFC 7396. Servers that handle both PUT and PATCH should check the Content-Type before dispatching: application/merge-patch+json → merge patch handler; application/json-patch+json JSON Patch (RFC 6902) handler; bare application/json on PATCH → return 415 Unsupported Media Type or treat as merge patch by convention.

JSON Merge Patch vs JSON Patch (RFC 6902)

Both standards solve partial document updates over HTTP, but they differ significantly in format, capability, and complexity. The table below covers every meaningful difference:

JSON Merge PatchJSON Patch
RFC7396 (2014)6902 (2013)
FormatPartial JSON objectArray of operation objects
Set nullNot possible (null = delete){"op":"add","path":"/key","value":null}
Delete keySet to null{"op":"remove","path":"/key"}
Array appendNot possible{"op":"add","path":"/arr/-","value":"x"}
Move/copyNot possible{"op":"move"} / {"op":"copy"}
ConditionalNot possible{"op":"test"}
ComplexitySimpleMore expressive
Content-Typeapplication/merge-patch+jsonapplication/json-patch+json

Decision rule: Use JSON Merge Patch for simple field updates — updating or deleting top-level or nested fields. Use JSON Patch (RFC 6902) when you need to insert into arrays, rename keys (move), set a field to null without deleting it, or apply conditional updates with test. For most REST APIs doing user profile updates, settings changes, or configuration patches, JSON Merge Patch is sufficient and much easier to implement and reason about.

Array replacement behavior — a critical limitation

JSON Merge Patch replaces arrays entirely. The algorithm has no concept of array element operations — there is no way to append, remove, or update a single array element. If the original document has "tags": ["a", "b", "c"] and the patch has "tags": ["a", "b"], the result is "tags": ["a", "b"] — element "c" is gone with no warning.

// Original
{ "id": 1, "tags": ["javascript", "node", "api"] }

// Patch — trying to add a tag
{ "tags": ["javascript", "node", "api", "rest"] }

// Result — entire array replaced (correct but verbose)
{ "id": 1, "tags": ["javascript", "node", "api", "rest"] }

// There is NO way to express "append 'rest' to tags" in merge patch.
// You must always send the complete new array.

To handle this correctly with JSON Merge Patch: fetch the current document, modify the array in your application code, and send the complete updated array in the patch. This introduces a read-modify-write cycle that can cause lost updates under concurrent writes — if two clients append a tag simultaneously, the second write will overwrite the first's addition. Protect against this with an ETag + If-Match header or optimistic locking in your API.

For fine-grained array operations (append/insert/remove by index), use JSON Patch (RFC 6902) instead. The operation {"op":"add","path":"/tags/-","value":"rest"} appends a single element to the array without touching any other elements. This is the primary reason to choose JSON Patch over JSON Merge Patch in practice: any endpoint that needs array element-level updates requires JSON Patch.

Generate a merge patch from two documents

When you have a before and after snapshot of a document (for example, after a user edits a form), you can compute the minimal merge patch that transforms the original into the updated version. This is useful when your client holds a full copy of the document and you want to send only the delta to the server. The algorithm: for each key in the target, include it if it differs from the original (recurse for nested objects); for each key in the original that is absent from the target, set it to null (to delete it on the server).

function generateMergePatch(original, target) {
  if (
    typeof original !== 'object' || typeof target !== 'object' ||
    original === null || target === null
  ) {
    return target
  }
  const patch = {}
  // Keys in target: add or recursively diff
  for (const key of Object.keys(target)) {
    if (JSON.stringify(original[key]) !== JSON.stringify(target[key])) {
      patch[key] = generateMergePatch(original[key], target[key])
    }
  }
  // Keys in original but not in target: set null to delete
  for (const key of Object.keys(original)) {
    if (!(key in target)) {
      patch[key] = null
    }
  }
  return patch
}

// Example
const original = { name: 'Alice', age: 30, phone: '+1-555-1234' }
const updated  = { name: 'Bob',   age: 30 }
generateMergePatch(original, updated)
// { name: 'Bob', phone: null }  ← minimal patch

The JSON.stringify comparison is a simple deep-equality check that works for most cases but is sensitive to key ordering in nested objects. For production use, prefer a proper deep-equality function or use the json-merge-patch npm package's generate() method, which handles edge cases correctly. You can also use Jsonic's JSON Diff tool to visualize the differences between two documents before computing a patch programmatically.

Frequently asked questions

What is JSON Merge Patch?

JSON Merge Patch is a format for partially updating a JSON document, defined in RFC 7396 by the IETF in October 2014. It is a simpler alternative to JSON Patch (RFC 6902). The patch itself is a JSON object that describes the changes: keys present in the patch update or add the corresponding keys in the target; keys set to null delete the corresponding key from the target; keys absent from the patch are left unchanged. Nested objects are merged recursively by the same rules. JSON Merge Patch is used with HTTP PATCH requests with Content-Type: application/merge-patch+json. The simplicity of the format — it looks like the document you want to end up with, minus the fields you don't want to change — makes it easy to construct and reason about without understanding a separate patch language. For a foundational understanding, see what is JSON.

What does null mean in JSON Merge Patch?

In JSON Merge Patch, a null value in the patch means "delete this key from the target." For example, applying {"phone": null} to {"name": "Alice", "phone": "555-1234"} produces {"name": "Alice"} — the phone key is removed entirely. This is a significant difference from regular JSON merging: null does not mean "set this key to null," it means "remove this key." As a consequence, JSON Merge Patch cannot be used to set a key to the JSON value null — there is no way to express "update this key to null" in a merge patch. If your data model requires nullable fields (fields that can legitimately be set to null), you need JSON Patch (RFC 6902) instead: {"op": "replace", "path": "/phone", "value": null} sets the field to null without deleting it.

How is JSON Merge Patch different from JSON Patch?

JSON Merge Patch (RFC 7396) and JSON Patch (RFC 6902) are two separate IETF standards for partially updating a JSON resource over HTTP, but with very different formats and capabilities. JSON Merge Patch is a partial JSON document: keys are set to their new values, null deletes a key, absent keys are unchanged. It is human-readable and easy to construct. JSON Patch is an array of explicit operation objects: add, remove, replace, move, copy, test. It supports fine-grained array operations (insert/remove by index), key renaming (move), and conditional updates (test). The tradeoff: JSON Merge Patch is much simpler to implement but cannot set a field to null, append to an array, or conditionally update. Use JSON Merge Patch for simple field updates in REST APIs. Use JSON Patch when you need precise array manipulation or conditional atomicity.

How do I apply JSON Merge Patch in JavaScript?

You can implement the RFC 7396 algorithm in about 10 lines without any library. The function takes the current document and the patch object. For each key in the patch: if the value is null, delete the key from the result; if both the patch value and the current value are objects, recursively merge them; otherwise, replace the current value with the patch value. Keys not present in the patch are preserved from the original. For npm packages, json-merge-patch (MIT license) provides mergePatch.apply(target, patch) and mergePatch.generate(original, target). The generate function computes the minimal patch that transforms one document into another, which is useful when you have a before/after snapshot of a document and need to send the delta over the wire. Use Jsonic's JSON formatter to validate and prettify your patch objects during development.

Can JSON Merge Patch update nested objects?

Yes, JSON Merge Patch recursively merges nested objects. If both the target and the patch have an object at the same key, the algorithm merges them recursively. Example: target is {"address": {"city": "Austin", "zip": "78701"}} and patch is {"address": {"city": "Dallas"}}. Result is {"address": {"city": "Dallas", "zip": "78701"}}city is updated, zip is preserved. However, if the patch replaces an object with a non-object (like a string), the entire object is replaced, not merged: patch {"address": "123 Main St"} replaces the entire address object with the string. Similarly, if the target has a non-object and the patch has an object at the same key, the target value is replaced with the patch object. Null at a nested level deletes that nested key: {"address": {"zip": null}} removes the zip key from the address object.

When should I use JSON Merge Patch instead of PUT?

HTTP PUT replaces the entire resource — you send the complete updated document. HTTP PATCH with JSON Merge Patch sends only the changed fields. Use PATCH + JSON Merge Patch when: (1) the document is large and you only need to change a few fields (bandwidth efficiency); (2) multiple clients may update different fields concurrently and you don't want to overwrite each other's changes; (3) you want to delete specific keys without replacing the whole document. Use PUT when: you want to replace the entire document atomically and ensure no undocumented fields persist; or your API is simple enough that sending the full document is not a problem. Be careful with PATCH and concurrent updates — if two clients patch different fields simultaneously, both updates can succeed independently. If you need "update only if the document hasn't changed," use JSON Patch's test operation or an ETag + If-Match header check. You can compare two JSON documents to verify the result of a patch before sending it.

Ready to work with JSON Merge Patch?

Use Jsonic's JSON Diff tool to compute the differences between two documents and generate your patch. You can also validate your JSON to ensure your patch is well-formed before sending it over the wire.

Open JSON Diff