Both identify something inside a JSON document, and they are easy to confuse because tools often show both. The same value, two notations:
JSONPath $['users'][0]['e-mail']
Pointer /users/0/e-mailThe difference is not cosmetic. They were designed for different jobs and only one of them is a standard.
The essential distinction
A Pointer is an address, like a street address: it names one place, unambiguously, and every reader resolves it identically. A JSONPath is a search, like "every house on this street with a red door" — far more expressive, and the answer depends on the searcher.
Use a Pointer when something must act on the value
Pointers are what the surrounding ecosystem speaks. If you are producing a reference that another system will consume, this is almost always the right choice.
- JSON Patch (RFC 6902) — every operation names its target with a Pointer.
- JSON Schema —
$refuses Pointer fragments, and validation errors report the failing location as one. - OpenAPI —
$ref: '#/components/schemas/User'is a Pointer. - Error reporting — "the value at
/items/3/priceis invalid" is unambiguous and machine-actionable.
[
{ "op": "replace", "path": "/users/0/email", "value": "new@example.com" },
{ "op": "remove", "path": "/users/2" }
]You could not write that with JSONPath. An operation that might match three values is not an operation — it is ambiguous, and a patch format cannot be ambiguous.
Use JSONPath when you are looking for something
Everything a Pointer deliberately cannot do:
$.users[*].email every email
$..id every "id" at any depth
$.users[?(@.active == true)] every active user
$.users[-1:] the last user
$.users[0,2] a union of twoThis is exploration and extraction — pulling a field out of every record, finding where a value lives, filtering a collection. The JSONPath evaluator runs these against your document and shows every match, with both notations for each result.
The escaping differs, and it matters
Pointer uses / as its separator and ~ as its escape character, so both need escaping when they appear in a key. Two rules, applied in this order when reading: ~1 means /, and ~0 means ~.
key "a/b", then key "c~d"
Pointer /a~1b/c~0d
JSONPath $['a/b']['c~d']The order matters when unescaping. Replace ~1 first and then ~0, and a literal ~1 in a key would be corrupted — so the rule is always ~1 → / before ~0 → ~. Most bugs in hand-written Pointer code are exactly this.
JSONPath avoids the question by using bracket notation with quoted keys, which is why results here are normalised to brackets rather than echoing the dot notation you typed.
Two more things worth knowing
A Pointer to the whole document is the empty string, not /. That trips people up: "" means the root, while "/" means the key whose name is the empty string — which is legal JSON and a genuinely different thing.
Array indices in a Pointer are plain numbers, and - means "the position after the last element", used by JSON Patch to append. There is no slicing and no negative indexing; /users/-1 is a key literally named -1, not the last item.
Choosing
- Will the result be consumed by another system — a patch, a schema, an error report? Pointer. It is standardised and everything understands it.
- Do you need more than one match, a filter or a wildcard? JSONPath. A Pointer cannot express it.
- Are you exploring a document to find something? JSONPath, then take the Pointer of the match you wanted for whatever you build next.
That third step is the common workflow and it is why both are shown side by side: query loosely to find the thing, then address it precisely. Click any node in the tree view to copy its path directly.