Files
json/docs/mkdocs/docs/features/merge_patch.md
T
Niels Lohmann 7f591adb55 docs: address review comments on the new design
- Home: add a "Latest release" button
- Sponsors: update the list and group it by tier
- Features: list all feature pages on the index page
- Releases: add highlights per release, link the 4.0 roadmap, add the first commit to the timeline
- Exceptions: prefix all example messages with the exception name
- API: no code font for format names in the See also links of the to_/from_ pages
- Merge Patch: show the document before, the patch, and the result
- Replace "--" with typographic dashes, mark external links with an icon, center Mermaid diagrams

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-26 21:15:53 +01:00

5.7 KiB

JSON Merge Patch

The library supports JSON Merge Patch (RFC 7396) as a patch format. The merge patch format is primarily intended for use with the HTTP PATCH method as a means of describing a set of modifications to a target resource's content. Applying a merge patch changes the JSON value the patch is applied to.

Instead of using JSON Pointer to specify values to be manipulated, it describes the changes using a syntax that closely mimics the document being modified. Unlike JSON Patch, a JSON Merge Patch cannot express every kind of change (e.g., it cannot reorder array elements or remove a specific array element), but it is easier to read and write for object-shaped documents.

The function merge_diff creates a merge patch that changes one JSON value into another.

The example below applies one patch to one document. Here is the document before:

{
    "title": "Goodbye!",
    "author": {
        "givenName": "John",
        "familyName": "Doe"
    },
    "tags": ["example", "sample"],
    "content": "This will be unchanged"
}

This is the patch:

{
    "title": "Hello!",
    "phoneNumber": "+01-123-456-7890",
    "author": {
        "familyName": null
    },
    "tags": ["example"]
}

And this is the document after applying it:

{
    "title": "Hello!",
    "author": {
        "givenName": "John"
    },
    "tags": ["example"],
    "content": "This will be unchanged",
    "phoneNumber": "+01-123-456-7890"
}

The table goes through the members one by one. "Member" is the name of an object member in the patch (and in the document); it is not a JSON Pointer and nothing of it is passed to the function: the whole patch is passed to merge_patch.

Rule Member Before Patch After
change value title #!json "Goodbye!" #!json "Hello!" #!json "Hello!"
add key phoneNumber (no member) #!json "+01-123-456-7890" #!json "+01-123-456-7890"
object merges recursively author #!json {"givenName": "John", "familyName": "Doe"} #!json {"familyName": null} #!json {"givenName": "John"}
null removes key familyName inside author #!json "Doe" #!json null (removed)
array replaced whole tags #!json ["example", "sample"] #!json ["example"] #!json ["example"]
unchanged key content #!json "This will be unchanged" (no member) #!json "This will be unchanged"

An array in the patch replaces the array in the target, so a merge patch cannot change a single element. Each member of the patch is applied like this; a patch that is not an object replaces the whole target.

flowchart LR
    M["member of the patch"] --> N{{"value null?"}}
    N -->|yes| D["delete the key"]
    N -->|no| O{{"value an object?"}}
    O -->|yes| R["merge recursively into the target's value"]
    O -->|no| S["set the key to the value"]

How one member of a merge patch is applied: a `null` value deletes the key, an object is merged recursively, and any other value replaces the value of the key.

!!! tip "Not a general deep merge"

A merge patch is not a general deep merge: a `#!json null` value in the patch deletes the key from the target.
To merge two objects recursively (e.g., defaults and user settings), use
[`update`](../api/basic_json/update.md) with `merge_objects` set to `#!cpp true`; see
[Merging objects](modifying_values.md#merging-objects).

!!! note "Limitations of RFC 7396"

- A patch cannot set a member to `#!json null`, because `#!json null` removes the member:
  `#!json {"a":1}` patched with `#!json {"a":null}` gives `#!json {}`.
- Arrays are replaced as a whole, never merged, and a `#!json null` inside an array is kept.
- `#!json null` members of the target that the patch does not mention are preserved.
- A `#!json null` that would remove a member of a not yet existing object still creates that object:
  `#!json {}` patched with `#!json {"a":{"b":null}}` gives `#!json {"a":{}}`.
- A patch that is not an object replaces the whole target (a `#!json null` patch makes the target
  `#!json null`); an object patch applied to a non-object target starts from an empty object.
- [`merge_diff`](../api/basic_json/merge_diff.md#notes) has a matching limitation for `#!json null` members.

??? example

The following code shows how a JSON Merge Patch is applied to a JSON document.

```cpp
--8<-- "examples/merge_patch.cpp"
```

Output:

```json
--8<-- "examples/merge_patch.output"
```

See also