Skip to main content

Updated Aug 7, 2026

JSON Pointer and JSON Patch

RFC 6901 (JSON Pointer) and RFC 6902 (JSON Patch) give you standardized addressing and diffing for JSON documents, so path-based access and structural diffs don't have to be hand-written traversal code.

JSON Pointer (RFC 6901)​

A JSON Pointer is a /-separated path into a document — /a/b/0 means "key a, then key b, then index 0":

{
"a": {
"b": [10, 20, 30]
}
}
json::json_pointer ptr("/a/b/0");
int v = doc.at(ptr).get<int>(); // 10, using at() with a pointer

int v2 = doc["/a/b/0"_json_pointer]; // same thing, via the literal

Two characters need escaping inside a pointer segment because they're part of the pointer syntax itself: ~ is written ~0 and / is written ~1 — so a key literally named "a/b" is addressed as /a~1b.

flatten() and unflatten()​

flatten() turns any nested document into a single flat object whose keys are JSON Pointers and whose values are the corresponding leaf values; unflatten() reverses it:

// before flatten()
{ "a": { "b": [10, 20] } }

// after flatten()
{ "/a/b/0": 10, "/a/b/1": 20 }

This round-trips exactly: doc.flatten().unflatten() == doc.

JSON Patch (RFC 6902)​

A JSON Patch is a JSON array of operations describing edits to apply to a document. The six operations are add, remove, replace, move, copy, and test:

patch.json
[
{ "op": "replace", "path": "/a/b/0", "value": 99 },
{ "op": "add", "path": "/a/c", "value": "new" },
{ "op": "remove", "path": "/a/b/1" }
]
json patch = json::parse(patch_text);
json patched = doc.patch(patch); // doc itself is unchanged; patched is the result

diff()​

json::diff(source, target) produces a JSON Patch that turns source into target:

json patch = json::diff(old_doc, new_doc);
json result = old_doc.patch(patch); // result == new_doc
diff output is minimal, not semantic

diff() produces a syntactically minimal patch by comparing document structure, not a semantically minimal one. Moving an array element from index 0 to index 2, for example, generally comes out as a sequence of replace operations on the affected indices rather than a single move — the algorithm doesn't attempt to recognize that the values were merely reordered. Don't rely on diff() output to convey developer intent, only to reproduce the resulting document.

Failure modes​

  • doc.at(bad_pointer) throws out_of_range when the path doesn't resolve to an existing element.
  • Constructing a json_pointer from a malformed string (missing leading /, invalid escape) throws parse_error.
  • A test operation in a patch whose value doesn't match the document at that path throws other_error when the patch is applied, aborting the whole patch() call.

See also​