JSON Patch vs Merge Patch: Which API Update Strategy Should You Use?

When updating a REST API resource, most developers default to sending the entire object with PUT. But there is a better way. PATCH requests let you update only what changed and there are two competing standards for describing those changes.

The Problem with PUT

Consider a User resource with 20 fields. When a user changes their display name, you have two options:

  • PUT: Send all 20 fields, even though only 1 changed
  • PATCH: Send only the 1 changed field + an update description

PATCH is clearly more efficient. But what format should the PATCH body use? That is where JSON Patch and Merge Patch diverge.

Merge Patch (RFC 7386)

Merge Patch is dead simple. Your PATCH body is a partial JSON object:

{
  "displayName": "Jane Doe",
  "preferences": {
    "theme": "dark"
  }
}

The server does a recursive merge: it replaces the specified fields, and deletes any fields set to null. That is it.

Merge Patch Rules

  • Set a field: include it with a non-null value
  • Delete a field: set it to null
  • Replace an object: set it to a new object (not merge)
  • Arrays: replaced entirely (not merged element-by-element)

Merge Patch Example

// Original
{ "name": "John", "age": 30, "address": { "city": "NYC" } }

// Merge Patch
{ "name": "Jane", "address": null }

// Result
{ "name": "Jane" }
// "age" is unchanged (stays 30)
// "address" is deleted
// "name" is replaced

When Merge Patch Works Well

  • Simple form updates (display name, email, preferences)
  • Resources with mostly flat structures
  • When you want to delete fields by setting them to null
  • Teams that find JSON Pointer syntax complex

When Merge Patch Breaks Down

  • Nested objects must be fully replaced (cannot merge one nested field)
  • Arrays are replaced entirely (no element-level updates)
  • Cannot express "delete this field but do not touch others"
  • Null has special meaning you cannot set a valid value to null

JSON Patch (RFC 6902)

JSON Patch is more powerful but more verbose. Your PATCH body is an array of operations:

[
  { "op": "replace", "path": "/displayName", "value": "Jane Doe" },
  { "op": "add", "path": "/preferences/theme", "value": "dark" },
  { "op": "remove", "path": "/legacyField" }
]

Each operation targets a location using JSON Pointer (RFC 6901), a path syntax like /users/0/email.

The Six JSON Patch Operations

  • add Insert or set a value at the target path
  • remove Delete the value at the target path
  • replace Replace an existing value (equivalent to remove + add)
  • move Move a value from one path to another
  • copy Copy a value to another path
  • test Assert a value equals the expected value (fails the patch if not)

Array Operations

Unlike Merge Patch, JSON Patch can update specific array elements:

[
  { "op": "add", "path": "/tags/-", "value": "featured" },
  { "op": "remove", "path": "/items/2" },
  { "op": "replace", "path": "/items/0/price", "value": 29.99 }
]

The - index in JSON Pointer means "append at end" a clever touch.

The Test Operation: Atomic Updates

The test operation enables optimistic concurrency control:

[
  { "op": "test", "path": "/version", "value": 7 },
  { "op": "replace", "path": "/displayName", "value": "Jane" },
  { "op": "replace", "path": "/version", "value": 8 }
]

If another request updated the version to 9 first, the test fails and the entire patch is rejected with 409 Conflict. Critical for collaborative editing.

Head-to-Head Comparison

AspectMerge PatchJSON Patch
FormatPartial JSON objectArray of operations
SpecRFC 7386RFC 6902
VerbosityConciseVerbose
Nested mergeNo (full replacement)Yes
Array operationsFull replacement onlyAdd/remove/replace elements
AtomicityNone built-invia test operation
Move/copyNoYes
Best forSimple flat updatesComplex nested updates

Real-World Adoption

  • GitHub API: JSON Patch for issues, PRs, and repository updates
  • Microsoft Graph: JSON Patch for OneDrive, Outlook, Teams updates
  • Kubernetes: Strategic Merge Patch (extends JSON Patch concepts)
  • Stripe API: Custom flat merge approach similar to Merge Patch
  • MongoDB: Merge Patch semantics in update operations

JavaScript Implementation

// JSON Patch with fast-json-patch (3M+ weekly downloads)
import * as jsonpatch from 'fast-json-patch';

const doc = { name: "John", age: 30 };

// Generate patch from changes
const observer = jsonpatch.observe(doc);
doc.name = "Jane";
doc.age = 31;
const patch = jsonpatch.generate(observer);
// [{op:"replace",path:"/name",value:"Jane"},{op:"replace",path:"/age",value:31}]

// Apply patch
jsonpatch.applyPatch(doc, [{ op: "replace", path: "/name", value: "Alice" }]);

// Merge Patch - just use spread operator
const mergePatch = (target, patch) => ({
  ...target,
  ...Object.fromEntries(
    Object.entries(patch).filter(([,v]) => v !== null)
  )
});

When to Use Which

Use Merge Patch when: Your resources are mostly flat, you want to delete fields by setting them to null, simplicity is more important than precision, or you are building mobile APIs where bandwidth matters.

Use JSON Patch when: Your resources have deep nesting, you need to update specific array elements, collaborative editing is a requirement, you need atomic updates with optimistic concurrency, or move and copy operations are needed.

Key Takeaways

  • Both PATCH approaches reduce bandwidth compared to full PUT
  • Merge Patch is simpler but less powerful good for basic CRUD
  • JSON Patch handles complex nested structures and array operations
  • The test operation in JSON Patch is a powerful concurrency tool
  • Most modern APIs benefit from JSON Patch for complex resources, Merge Patch for simple ones
  • You can mix approaches: Merge Patch for simple endpoints, JSON Patch for complex ones

You May Also Like