Sitelet https://www.jsonpatchonline.com/docs/json-patch-nested-objects

Documentation

JSON Patch and Nested Objects

Real documents are deep. Updating a nested JSON property with JSON Patch means walking the path segment by segment - and knowing the few rules that decide whether a deep path works.

To update a nested property with JSON Patch, aim any operation at the full JSON Pointer path: { "op": "replace", "path": "/user/address/city", "value": "Boston" }. Depth changes nothing about the operations - only about the path - but every segment before the last one must already exist.

Updating a nested property

Patch

[
  { "op": "replace", "path": "/user/address/city", "value": "Boston" }
]

Before

{
  "user": {
    "name": "Ana",
    "address": { "city": "NYC", "zip": "10001" }
  }
}

After

{
  "user": {
    "name": "Ana",
    "address": { "city": "Boston", "zip": "10001" }
  }
}

The pointer walks user, then address, then city. Everything around the path - name, zip - is untouched.

Adding deep: parents must exist

add can create the value at the end of a path, but not the containers on the way. To add /user/address/country when address exists, one operation is enough. When it does not, build the structure first:

Patch

[
  { "op": "add", "path": "/user/address", "value": {} },
  { "op": "add", "path": "/user/address/city", "value": "Boston" },
  { "op": "add", "path": "/user/address/zip", "value": "02108" }
]

You can also add the whole subtree in one operation - add /user/address with the full object as the value. Both are correct; the step-by-step version is easier to guard with test.

Nested arrays

Object keys and array indexes mix freely in one path. Append to an array two levels down, or update a field of one array element:

Patch

[
  { "op": "add", "path": "/user/tags/-", "value": "vip" },
  { "op": "replace", "path": "/users/0/email", "value": "new@example.com" }
]

All the array rules - /-, index shifting, no wildcards - apply at any depth. See JSON Patch and arrays.

Moving and copying whole subtrees

move and copy work on entire subtrees, not just scalars. Moving a nested object relocates everything under it:

Patch

[
  { "op": "move", "from": "/user/address", "path": "/shipping/address" }
]

After this, /user/address no longer exists - any later operation pointing there fails. And the destination can never be inside the source: moving /user to /user/profile is rejected. The same goes for remove: removing a parent takes the whole subtree with it.

Guarding deep updates with test

The deeper the path, the more a document can drift from your assumption. A test on the nested value fails the patch instead of writing into the wrong state:

Patch

[
  { "op": "test", "path": "/user/address/zip", "value": "10001" },
  { "op": "replace", "path": "/user/address/city", "value": "Boston" }
]

Common mistakes

Assuming add creates the intermediate keys

add creates only the final step of a path. /user/address/city fails when /user/address does not exist - create the parents with earlier add operations first.

Addressing a child after removing its parent

Operations run in order. After remove /user, any path starting with /user fails. Remove the parent last, or remove only the child keys you actually want gone.

Replacing a whole object when you meant one property

replace /user/address swaps the entire address object - every key you omit disappears. To change one property, target it: replace /user/address/city.

Forgetting arrays inside nested objects

A path like /users/0/tags/- mixes object keys and array indexes freely - but each segment is checked against the container at that level. An object key where an array sits (or vice versa) fails.

Reference

Path evaluation is defined by RFC 6901, section 4, and the parent-existence rule for add by RFC 6902, section 4.1.

Generate a nested patch yourself

Open the generator with a nested before/after preloaded - the patch it emits uses the full deep path.

Open the generator with this example