Skip to content
JSONCraft

Fundamentals

JSON Pointer and JSON Patch — Worked Example

Copy a JSON Pointer path and understand all six JSON Patch operations with a starting document and a reproducible result.

JSON Pointer identifies a value by its path. JSON Patch describes a sequence of changes using those paths. JSONPath serves a different purpose: selecting sets of values with queries.

Try the same document in the visualizer

Open the JSON visualizer, choose Sample, and select a node. Copy Pointer returns its path; Copy JSON value returns its contents. The graph is a bounded view; copies preserve the complete value.

Starting document:

{
  "users": [
    {
      "id": 1,
      "name": "Ana"
    },
    {
      "id": 2,
      "name": "Ben"
    }
  ],
  "a/b~c": {
    "": "value"
  },
  "version": 1
}

Paths to check

Scroll horizontally to see all columns.

Pointer (shown as a JSON string) Value
"" Whole document
"/users/0/name" "Ana"
"/users/1/id" 2
"/a~1b~0c/" "value"

The last path includes a key containing / and ~, followed by an empty key. Escape each segment by replacing ~ with ~0, then / with ~1. Decode in the reverse order: ~1, then ~0. This makes ~01 refer to the literal key ~1.

"" differs from "/": the latter selects an empty-name property at the root. Array indices start at zero and cannot contain leading zeroes: /users/01 is invalid for that array. The - token does not resolve an existing element; JSON Patch uses it for appending.

This is the string form of RFC 6901. A URI fragment such as #/users/0/name also requires appropriate URI encoding; do not add # to JSON Patch path fields. When representing a Pointer inside JSON, apply JSON string escaping to quotation marks and backslashes too.

A complete Patch: six operations and the result

Apply this array to the starting document above, in order:

[
  {
    "op": "test",
    "path": "/version",
    "value": 1
  },
  {
    "op": "replace",
    "path": "/users/0/name",
    "value": "Alicia"
  },
  {
    "op": "add",
    "path": "/users/-",
    "value": {
      "id": 3,
      "name": "Cyr"
    }
  },
  {
    "op": "copy",
    "from": "/a~1b~0c/",
    "path": "/snapshot"
  },
  {
    "op": "move",
    "from": "/users/1",
    "path": "/users/0"
  },
  {
    "op": "remove",
    "path": "/users/2"
  },
  {
    "op": "replace",
    "path": "/version",
    "value": 2
  }
]
  • test checks the version before changing anything.
  • replace changes Ana’s name.
  • add appends Cyr to the array.
  • copy copies the empty-key value to snapshot.
  • move removes Ben from his position and inserts him at the beginning.
  • remove deletes Cyr, who is still at index 2.
  • The final replace updates the version.

Expected result:

{
  "users": [
    {
      "id": 2,
      "name": "Ben"
    },
    {
      "id": 1,
      "name": "Alicia"
    }
  ],
  "a/b~c": {
    "": "value"
  },
  "version": 2,
  "snapshot": "value"
}

Each operation sees the result of the preceding one; indices shift on insertion or removal. add can replace an existing object member, but inserts into arrays. Its parent must exist. replace and remove require an existing target. copy and move require from; moving a node into its own descendant is invalid. These rules are defined by RFC 6902.

Errors and atomicity

Changing the first test to value: 99 makes this example fail before changing anything. That does not guarantee rollback in every library: an in-place application can fail after earlier operations have already changed data. Work on a copy and commit the result only when all operations succeed.

HTTP PATCH requires atomic application. For an API, send Content-Type: application/json-patch+json when the server supports that format. If-Match with the received ETag helps avoid overwriting a different version; the server must enforce it. A 412 indicates a failed HTTP precondition, not every possible JSON Patch error.

When to use Merge Patch

JSON Merge Patch, RFC 7396, uses application/merge-patch+json. Within an object, null removes a property instead of setting its value to null. Arrays are replaced as a whole, with no index operations. A non-object patch replaces the entire document, even when that patch is null.

For field updates this can be simpler; use JSON Patch when setting a property to null or editing array positions matters.

Continue checking

Compare the starting document and result in JSON Diff. To select multiple matches, use JSONPath. Neither tool applies this guide’s Patch; check the mutation options and behavior of your chosen library. A patch generator is not guaranteed to produce the smallest possible patch.

Related tools

Frequently asked questions

Is the root pointer /?

No. The root is the empty string. / selects a property whose name is empty.

Does the visualizer apply JSON Patch?

No. It explores JSON and copies paths and values. This guide shows operations and their result; JSON Diff compares documents and does not generate or apply a Patch.

Does a library roll back changes after an error?

Do not assume so. HTTP PATCH requires atomic application, but an in-place library may leave partial changes. Apply to a copy and commit only after all operations succeed.

By