Try the JSON Unescape & Cleaner

JSON Patch, Merge Patch, JSON Pointer, and JSON Path: The Standards Most Developers Don't Know

JSON Patch (RFC 6902) sends a list of operations (add, remove, replace, test) to modify a resource atomically. JSON Merge Patch (RFC 7396) uses a simpler merge rule. Here's both standards with examples, JSON Pointer path syntax, JSON Path for querying, and why the `test` operation enables optimistic concurrency.

By sadiqbd Β· June 14, 2026

JSON Patch, Merge Patch, JSON Pointer, and JSON Path: The Standards Most Developers Don't Know

JSON Patch lets you send only what changed β€” and most REST APIs don't support it yet, even though they should

The standard REST pattern for updating a resource is PUT (replace the entire resource) or PATCH (partial update). Most PATCH implementations accept a partial JSON body and merge it informally β€” there's no standard for how the merge works. JSON Patch (RFC 6902) and JSON Merge Patch (RFC 7396) are the two standardised approaches, and knowing them changes how you design APIs that handle partial updates cleanly.


The problem with informal PATCH

When a REST API documents PATCH /users/{id}, the typical implementation accepts whatever fields you send and updates them:

PATCH /users/usr_4821
{
  "email": "newemail@example.com"
}

This updates the email. But what does this mean?

PATCH /users/usr_4821
{
  "preferences": {
    "theme": "dark"
  }
}

Does it replace the entire preferences object (removing notifications, language etc.)? Or merge into it? The answer is implementation-specific and often undocumented. JSON Merge Patch defines this precisely.


JSON Merge Patch (RFC 7396): the simpler standard

JSON Merge Patch uses a simple rule: the provided object is merged into the existing resource. To remove a key, set it to null.

Rules:

  1. For each key in the patch:
    • If the value is null: remove that key from the resource
    • If the value is a JSON object: recursively merge
    • Otherwise: replace the value in the resource

Example:

Original resource:

{
  "name": "Alice",
  "email": "alice@example.com",
  "preferences": {
    "theme": "light",
    "notifications": true,
    "language": "en"
  },
  "active": true
}

Merge Patch:

{
  "email": "alice@newdomain.com",
  "preferences": {
    "theme": "dark"
  },
  "active": null
}

Result:

{
  "name": "Alice",
  "email": "alice@newdomain.com",
  "preferences": {
    "theme": "dark",
    "notifications": true,
    "language": "en"
  }
}
  • email was updated
  • preferences.theme was updated; other preferences keys preserved (recursive merge)
  • active was removed (null means delete)
  • name was untouched (not in patch)

Content-Type: application/merge-patch+json

Limitation: you can't use Merge Patch on resources where null is a legitimate field value (because null means "delete"). For those resources, JSON Patch is necessary.


JSON Patch (RFC 6902): operations-based

JSON Patch expresses changes as an array of operation objects. Each operation has a op, path, and (where applicable) value or from.

Operations:

Op Action
add Add a value at path (creates key if absent, appends to array)
remove Remove the value at path
replace Replace the value at path (must exist)
move Move a value from one path to another
copy Copy a value from one path to another
test Test that a value at path equals a given value (fails patch if not)

JSON Pointer (RFC 6901): paths use the /key/subkey format. /0 for array index 0. /- for "append to end of array".

Example operations:

[
  { "op": "replace", "path": "/email", "value": "alice@newdomain.com" },
  { "op": "replace", "path": "/preferences/theme", "value": "dark" },
  { "op": "remove", "path": "/active" },
  { "op": "add", "path": "/preferences/timezone", "value": "Europe/London" },
  { "op": "test", "path": "/name", "value": "Alice" }
]

The test operation: checks a precondition before applying the patch. If Alice's name has changed since you fetched the resource, the test fails and the entire patch is rejected atomically. This is the optimistic concurrency mechanism built into JSON Patch β€” you can verify state before modifying it, without needing a separate version field.

Content-Type: application/json-patch+json


JSON Pointer (RFC 6901)

JSON Pointer is the path syntax used in JSON Patch and referenced in JSON Schema $ref resolution.

Syntax:

  • Empty string "": refers to the whole document
  • /foo: the key foo
  • /foo/bar: nested key bar within foo
  • /foo/0: index 0 of array foo
  • /a~1b: key a/b (the / in the key is escaped as ~1)
  • /a~0b: key a~b (the ~ is escaped as ~0)
{
  "store": {
    "books": [
      {"title": "Foundation", "price": 12.99},
      {"title": "Dune", "price": 14.99}
    ]
  }
}
  • /store/books/0/title β†’ "Foundation"
  • /store/books/1/price β†’ 14.99
  • /store/books/- β†’ append position (for add operations)

JSON Path: querying, not just addressing

JSON Pointer addresses a specific location. JSON Path (RFC 9535, standardised 2024) queries β€” it can match multiple nodes and supports filtering.

Syntax examples:

$.store.books[*].title         // All book titles
$.store.books[0].title         // First book title
$.store.books[?(@.price < 13)] // Books cheaper than $13
$..title                       // All 'title' values anywhere in document
$.store.books[-1:]             // Last book

Where JSON Path is used:

  • Kubernetes: pod spec JSONPath queries (kubectl get pods -o jsonpath='{.items[*].metadata.name}')
  • AWS IAM: resource conditions reference JSON Path
  • Grafana: JSON datasource queries
  • OpenAPI / JSON Schema: jsonpath filters in some tools

Library support

JavaScript (fast-json-patch):

const jsonpatch = require('fast-json-patch');

const document = { name: "Alice", email: "alice@example.com" };
const patch = [
  { op: "replace", path: "/email", value: "alice@new.com" },
  { op: "add", path: "/age", value: 30 }
];

const result = jsonpatch.applyPatch(document, patch);

Python (jsonpatch):

import jsonpatch

doc = {"name": "Alice", "email": "alice@example.com"}
patch = jsonpatch.JsonPatch([
    {"op": "replace", "path": "/email", "value": "alice@new.com"},
    {"op": "add", "path": "/age", "value": 30}
])
result = patch.apply(doc)

How to use the JSON Unescape tool on sadiqbd.com

When working with JSON Patch:

  1. Paste escaped JSON Patch payloads from API logs β€” remove escaping to read the operations clearly
  2. Clean double-encoded patches β€” when a JSON Patch array has been JSON-stringified (common when embedded in another JSON body)
  3. Debug path issues β€” format the patch operations to verify paths are correct

Frequently Asked Questions

Should I use JSON Patch or JSON Merge Patch for a new API? JSON Merge Patch for most cases β€” simpler, easier to understand, and sufficient for the majority of update patterns. JSON Patch when you need: array element modification by index, atomic test-and-set operations, move/copy operations, or when null is a valid field value in your schema.

Does HTTP PATCH require a specific body format? No β€” PATCH is defined to accept any format that the server understands. The Content-Type header specifies the format: application/json-patch+json for JSON Patch, application/merge-patch+json for Merge Patch, or application/json for informal partial updates.

Is the JSON Unescape tool free? Yes β€” completely free, no sign-up required.

Try the JSON Unescape tool free at sadiqbd.com β€” remove JSON escaping, decode double-encoded JSON, and clean any escaped JSON string instantly.

Try the related tool:
Open JSON Unescape & Cleaner

More JSON Unescape & Cleaner articles