Home/Blog/How to Generate and Apply a JSON Patch (RFC 6902) โ€” With Real Examples
๐Ÿฉน
json patchrfc 6902http patch request

How to Generate and Apply a JSON Patch (RFC 6902) โ€” With Real Examples

A practical guide to JSON Patch: what the six operations actually do, how to write a real HTTP PATCH request body, and the mistakes that cause a patch to fail against a document that looks like it should accept it.

October 7, 20265 min readby ToolNinja

The Problem JSON Patch Solves

Updating part of a resource over HTTP has three common approaches, and two of them have real problems. Resending the entire resource (PUT) works but wastes bandwidth and risks clobbering a field someone else updated concurrently. A bespoke "partial update" JSON body works until every client and server has to agree on an undocumented convention for what a missing field means versus an explicit null. JSON Patch (RFC 6902) is the standardized middle ground: an explicit, ordered list of operations describing exactly what changed.


The Six Operations

[
  { "op": "add", "path": "/active", "value": true },
  { "op": "remove", "path": "/legacyField" },
  { "op": "replace", "path": "/role", "value": "senior engineer" },
  { "op": "move", "from": "/tempName", "path": "/name" },
  { "op": "copy", "from": "/address", "path": "/billingAddress" },
  { "op": "test", "path": "/version", "value": 3 }
]
OperationWhat it does
addInserts a value at path. On an object, creates or overwrites a key. On an array, inserts at that index (shifting later elements right).
removeDeletes the value at path.
replaceEquivalent to a remove followed by an add at the same path โ€” the target must already exist.
moveRemoves the value at from and adds it at path โ€” a single atomic operation, not two separate ones you write yourself.
copyLike move, but leaves the original at from untouched.
testAsserts the value at path equals the given value โ€” if it doesn't match, the entire patch application fails before any further operations run.

Paths Are JSON Pointers (RFC 6901), Not JSONPath

This trips people up specifically because both look similar and both show up in JSON tooling. A JSON Patch path is a JSON Pointer:

/user/address/city       โ†’ object property access, slash-separated
/items/2                 โ†’ array index (zero-based)
/items/-                 โ†’ "append" โ€” only valid with add
/a~1b                     โ†’ the literal key "a/b" โ€” ~1 escapes a literal slash
/m~0n                     โ†’ the literal key "m~n" โ€” ~0 escapes a literal tilde

JSONPath ($.user.address.city, used by tools like the JSONPath Tester) is a separate, more expressive query language for finding values. JSON Pointer is deliberately simpler โ€” it addresses exactly one location, with no wildcards or filtering โ€” which is exactly the precision a patch operation needs.


Writing the Actual HTTP Request

PATCH /api/users/42 HTTP/1.1
Content-Type: application/json-patch+json

[
  { "op": "replace", "path": "/role", "value": "senior engineer" },
  { "op": "add", "path": "/tags/-", "value": "rust" }
]

The Content-Type: application/json-patch+json header matters โ€” it's a distinct media type from application/json, and a server implementing RFC 6902 correctly may reject a patch body sent with the generic JSON content type, since that's conventionally reserved for a full-resource representation (what a PUT would send) or a JSON Merge Patch.


Generating a Patch From a Before/After Example

Hand-writing operations for a large nested object is tedious and error-prone. The practical approach: take your "before" document, make the change you want in a copy, and diff the two into a patch automatically.

before:  { "name": "Jane Doe", "role": "engineer", "tags": ["backend", "go"] }
after:   { "name": "Jane Doe", "role": "senior engineer", "tags": ["backend", "go", "rust"] }

generated patch:
[
  { "op": "replace", "path": "/role", "value": "senior engineer" },
  { "op": "add", "path": "/tags/2", "value": "rust" }
]

This is almost always faster and less error-prone than writing the operations by hand, especially for anything beyond a one-field change.


Why a Patch Fails to Apply

The most common failure mode: the patch was generated against (or hand-written assuming) a different version of the document than the one it's actually being applied to.

Error: Path "/role" does not exist โ€” cannot replace.

Every operation except add expects its target path to already exist in the document. If a field was renamed, removed, or the document structure otherwise drifted between when the patch was created and when it's applied, the operation at that path fails โ€” and a well-implemented patch applier should name the exact path and what it expected to find there, not fail silently or apply a partial, inconsistent result.

This is exactly what the test operation is for in a concurrent-update scenario: assert the document is still at the version you expect before making changes, so a conflicting concurrent update causes a clean, explicit failure instead of silently overwriting someone else's change.


Quick Reference

QuestionAnswer
Which media type for the HTTP body?application/json-patch+json
How do I target an array element?/arrayName/2 (zero-indexed)
How do I append to an array?/arrayName/- with op: "add"
How do I escape a / or ~ in a key name?~1 for /, ~0 for ~
How do I guard against a stale/concurrent update?A test operation asserting the expected current value, before your real changes

Try It

ToolNinja's JSON Patch Tool โ†’ generates a patch from two example documents, or applies an existing patch to a document and shows the resulting output โ€” with a specific error naming the exact path when a patch doesn't apply cleanly. JSON Diff Checker โ†’ also exports its comparison as a JSON Patch directly, if you just need the diff in that format.


Sources:

Share:๐• Twitterin LinkedIn

Frequently Asked Questions

What's the difference between JSON Patch and JSON Merge Patch?

JSON Patch (RFC 6902) is an explicit, ordered list of operations with JSON Pointer paths โ€” precise, and able to target specific array elements or assert preconditions, but more verbose. JSON Merge Patch (RFC 7396) is just a partial object merged into the target โ€” more compact, but it can't remove a key without a special null convention and can't address individual array elements at all. Use JSON Patch whenever arrays are involved or you need the 'test' operation's precondition check.

What Content-Type does a JSON Patch HTTP request use?

application/json-patch+json โ€” a dedicated media type, distinct from plain application/json. Sending a JSON Patch body with the wrong Content-Type is a common cause of a server rejecting an otherwise well-formed request.

Can a JSON Patch operation target an array index?

Yes โ€” a path like /items/2 targets the third element (zero-indexed) of the items array. The special index - (a literal hyphen) means 'append to the end of the array,' used specifically with the add operation.

Why does my patch fail with a 'path does not exist' error?

The document you're applying the patch to doesn't match what the patch expects to find at that path โ€” almost always because the patch was generated against a different version of the document than the one it's being applied to. Operations other than add expect the target path to already exist.

๐Ÿฅท ToolNinja