The Problem: How Do You Update Part of a JSON Document?

When building APIs, you often need to update individual fields in an object without touching the rest. The naive approach is to send the entire object with just one field changed, but this requires the client to track state, increases bandwidth, and creates race conditions when multiple clients update the same object simultaneously. JSON Merge Patch (RFC 7386) provides a simple solution: send only the fields you want to change, and the server merges them into the existing document. Deleted fields are represented by null in the patch.

JSON Merge Patch (RFC 7396): Partial Updates Made Simple

JSON Merge Patch defines a straightforward format for describing partial modifications to a JSON document. Unlike JSON Patch (RFC 6902), which uses an array of operations (add, remove, replace, move, copy, test), Merge Patch describes the desired end state by providing a partial JSON object that mirrors the target structure. Properties present in the patch overwrite the target; properties set to null delete the target property; properties not mentioned in the patch remain unchanged.

The simplicity of Merge Patch makes it ideal for REST APIs where clients send partial updates. A PATCH request to update a user's email address would include only the changed field: {"email": "[email protected]"}. The server merges this with the existing user document, updating only the email field. This approach is intuitive and self-documenting—the patch body looks exactly like the portion of the resource being modified.

However, Merge Patch has limitations. It cannot distinguish between "set field to null" and "delete field"—both use null in the patch. It cannot modify individual array elements without specifying the entire array. For complex partial updates, especially those involving arrays or conditional operations, JSON Patch provides more precise control.

Key Insight: JSON Merge Patch (RFC 7396) is intentionally simple—it replaces top-level fields but cannot express "remove this element from an array." For array manipulation and complex transformations, use JSON Patch (RFC 6902) instead.

Implementing Efficient Merge Algorithms

A naive recursive merge implementation risks stack overflow on deeply nested documents and creates unnecessary object copies. An optimized implementation uses an iterative approach with a manual stack, supporting documents of arbitrary depth. The algorithm walks both the target and patch documents in parallel, building the result incrementally. Property accesses are cached using hash maps to avoid repeated string lookups in large objects.

Conflict resolution strategies depend on application requirements. The standard merge simply overwrites, but real-world applications often need more sophisticated strategies. Last-write-wins (LWW) uses timestamps or version vectors to resolve conflicts. Three-way merging uses a base version plus two modified versions to compute the merge result, similar to how Git resolves merge conflicts. Operational transformation (OT) and Conflict-free Replicated Data Types (CRDTs) provide mathematical guarantees about merge consistency in distributed systems.

JSON Merge Patch vs. JSON Patch: When to Use Each

The choice between Merge Patch (RFC 7396) and JSON Patch (RFC 6902) depends on the nature of your updates. Merge Patch excels when clients modify a known subset of fields—updating a user profile, changing configuration values, or patching document metadata. Its key advantage is simplicity: the patch body is a partial JSON object that looks exactly like the fields being changed, making it trivially debuggable with any JSON formatter.

JSON Patch, by contrast, uses an array of explicit operations. Each operation specifies an op (add, remove, replace, move, copy, test), a JSON Pointer path, and optionally a value. This verbosity enables precise array manipulation—removing the third element of an array, inserting at a specific index, or moving a value between paths. The cost is complexity: a simple field update that takes one line in Merge Patch requires a multi-line operation array in JSON Patch.

A practical decision framework: use Merge Patch for CRUD-style REST APIs where resources have stable schemas and clients update known fields. Use JSON Patch when you need array element manipulation, conditional updates (the test operation fails the entire patch if a precondition is not met), or when you are implementing collaborative editing features that require fine-grained change tracking.

Implementing Merge Patch in Node.js and Python

A correct Merge Patch implementation is concise but must handle edge cases. Here is a production-ready implementation in JavaScript that handles null deletion, nested objects, and type mismatches:

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

In Python, the same algorithm using type guards:

def merge_patch(target, patch):
    if patch is None:
        return None
    if not isinstance(patch, dict):
        return patch
    if not isinstance(target, dict):
        target = {}
    result = dict(target)
    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

Both implementations handle the critical edge case where the patch specifies a scalar value for a key that was previously an object—the entire subtree is replaced, not merged. This is by design in RFC 7396 and prevents unintended deep merges when the client intends a full replacement.

You May Also Like