Both JSON Pointer and JSONPath address locations inside a JSON document, and
both show up in the same kinds of tools: $ref in JSON Schema, PATCH
operations, path-based diffing. They solve different problems, and knowing
which one a given task actually calls for saves picking the wrong tool for
it.
JSON Pointer: one location, RFC 6901
A pointer is a sequence of / separated tokens: /user/name means the
name key inside the user object, /items/0 means the first element of
the items array. Two characters need escaping inside a token (~1 for a
literal /, ~0 for a literal ~), and the empty string as a pointer
means the whole document. That is the entire grammar. A pointer resolves to
exactly one value, or fails, with nothing in between: there is no wildcard,
no filter, no way to match more than the one location it names.
{ "user": { "name": "Ada", "roles": ["admin", "editor"] } }
/user/name resolves to "Ada". /user/roles/1 resolves to "editor".
/user/email fails, because that key does not exist. Nothing in the
pointer syntax can ask for “every role” in one expression; the caller
already has to know the index.
JSONPath: a query language
JSONPath, standardized as RFC 9535, is built for matching, not just
addressing. $.user.roles[*] returns both roles in the example above in
one query. $..email searches every level of the document for any key
named email, however deep it is nested. Filter expressions like
$.items[?(@.price > 100)] select array elements by a condition on their
own content. A JSONPath query can return zero matches, one, or many, which
is a fundamentally different contract from a pointer’s resolve-or-fail.
The practical difference
A pointer names a location you already know. A JSONPath query describes a condition and lets the engine find what satisfies it. That difference decides which one fits a given job:
- You already know exactly which value you want (“the third element of the shipping address array”): use a pointer. It is simpler, has one unambiguous meaning per document, and every implementation agrees on what it does, since the RFC leaves little room for dialect differences.
- You are searching for values that match a condition, or you do not know
the exact path in advance (“every object with a
statusoffailed, wherever it appears”): reach for JSONPath. This is also where dialect differences start to matter: JSONPath implementations vary more than RFC 9535’s own examples suggest, so a query written for one library is not guaranteed to behave identically in another.
Where each shows up in practice
JSON Patch (RFC 6902) operations use pointers exclusively; every "path"
in an add, remove, replace or move operation is a JSON Pointer,
because a patch operation targets one exact location by design. JSON
Schema’s $ref keyword also resolves via pointer syntax, for the same
reason: a schema reference points at one definition, not a set of them.
JSONPath shows up wherever a person or a tool needs to query a document
interactively: debugging a large API response, extracting a field from
logs, or building a rule that should match any document with a given
shape regardless of exactly where the relevant field sits.
Trying both
The JSON Pointer Evaluator resolves a pointer against a document and, on a failed lookup, reports how far it got and what keys actually exist at that point, rather than a bare “not found.” The JSONPath Tester runs the documented subset of RFC 9535 with function extensions, and the JSONPath cheat sheet on this site’s own blog has a worked example for every selector worth knowing. How to use JSON Pointer covers the pointer syntax itself in more depth, escapes included.