JSONPath is a query language for selecting nodes from a JSON value. It is inspired by XPath, but its syntax and result model are specific to JSON. The name historically covered several incompatible implementations; RFC 9535 now defines a standard JSONPath syntax and semantics, while libraries may still expose extensions such as regular-expression operators, aggregation functions, scripts, callbacks, or mutation.
Key Takeaways
- Check the dialect before sharing a query. A path that works in one library may be an extension or have different result ordering elsewhere.
- JSONPath is primarily a read/query language. Updating data, invoking callbacks, and evaluating scripts are implementation features with separate safety risks.
$, child selectors, wildcards, descendant selectors, indices, slices, and filters are useful primitives; broad recursive descent can be expensive.- JSONPath returns selected values/nodes, while JSON Pointer identifies one location and JMESPath emphasizes projections and transformations. They are not interchangeable.
- Treat user-supplied expressions and filter values as untrusted input. Use an allowlist, disable script/function extensions, and enforce depth, result, time, and memory limits.
- Query success does not authorize an object or prove that a returned value is safe to expose. Apply tenant, field, and purpose authorization before and after evaluation.
JSONPath and Its Standard
The original JSONPath proposal from 2007 influenced many libraries, but it did not define one universally implemented grammar. RFC 9535 provides a standard query model with a root identifier $, child and descendant segments, wildcards, array selectors, and filter expressions.
The standard does not make every historical feature portable. Before using a path in an API, record:
- the dialect and library version;
- whether results preserve node order or duplicates;
- how missing values and scalar roots are handled;
- which filter operators and function extensions are enabled;
- maximum query and result limits.
Core Selectors
Consider:
{
"store": {
"books": [
{ "title": "Systems", "price": 79.99, "inStock": true },
{ "title": "Algorithms", "price": 89.99, "inStock": false }
],
"location": { "city": "San Francisco" }
}
}
| Query | Intent |
|---|---|
$ |
Select the root value |
$.store |
Select a child member |
$['store']['location']['city'] |
Select a member using bracket notation |
$.store.books[0] |
Select an array index |
$.store.books[*].title |
Select titles from all array elements |
$..price |
Recursively select matching member names |
$.store.books[0:2] |
Select a slice where supported by the dialect |
Bracket notation is useful for keys containing spaces, punctuation, or characters that dot notation cannot express. Slice syntax and index bases must be checked against the implementation and standard profile you deploy.
Filters and Expressions
A filter evaluates a predicate against candidate nodes. A portable filter should use simple comparisons and explicit existence semantics:
$.store.books[?(@.price > 80 && @.inStock == false)].title
Do not assume that every engine supports:
- regular-expression operators such as
=~; - arbitrary script expressions;
- user-defined functions;
- aggregation functions such as
avg()orstddev(); - mutation methods such as
apply()orupdate().
Those features can change the language from a bounded selector into an expression runtime. If a product needs them, define a safe subset and test it as a separate contract.
Result Semantics
A query can return zero, one, or many matches. An implementation may return values, paths, nodes, or a wrapper containing all three. Decide:
- whether a missing path is an empty result or an error;
- whether duplicate matches are retained;
- whether order follows document traversal;
- whether a scalar root can be queried;
- how
nulldiffers from no match; - whether results may contain references to mutable in-memory objects.
For API assertions, make cardinality explicit. “The query returned an empty list” is not always the same as “the required field was absent.”
JSONPath, JSON Pointer, and JMESPath
| Tool/language | Primary role | Typical result |
|---|---|---|
| JSONPath | Select one or many nodes from a JSON tree | Values/nodes/paths, depending on implementation |
| JSON Pointer (RFC 6901) | Identify one location with escaped tokens | A location such as /store/books/0/title |
| JMESPath | Query plus projections and transformations | A computed JSON value |
| JSON Schema | Assert structure and constraints | Valid/invalid plus errors |
Use JSON Pointer when a patch or authorization policy needs an unambiguous location. Use JSON Schema for validation. Do not use a JSONPath query as a substitute for object ownership or field-level authorization.
JavaScript Example with a Pinned Library
The library API and filter syntax must be pinned and tested. This example uses a read-only query; it does not accept a query from an untrusted caller.
import { JSONPath } from "jsonpath-plus";
const data = {
store: {
books: [
{ title: "Systems", price: 79.99, inStock: true },
{ title: "Algorithms", price: 89.99, inStock: false },
],
},
};
const titles = JSONPath({
path: "$.store.books[*].title",
json: data,
});
const unavailable = JSONPath({
path: "$.store.books[?(@.inStock == false)].title",
json: data,
});
console.log({ titles, unavailable });
Do not enable script or callback features for untrusted paths unless the library provides a narrowly isolated evaluator. A callback can turn a read query into a side-effect boundary.
Python and Java Notes
Python libraries such as jsonpath-ng expose a core parser and an extended parser with different grammar. Keep imports, package versions, and extensions explicit:
from jsonpath_ng import parse
data = {
"store": {
"books": [
{"title": "Systems", "price": 79.99},
{"title": "Algorithms", "price": 89.99},
]
}
}
expression = parse("$.store.books[*].title")
titles = [match.value for match in expression.find(data)]
print(titles)
In Java, Jayway JsonPath and other libraries have different providers, configuration defaults, filter behavior, and return types. Pin the dependency, configure the JSON provider, and test paths against fixtures rather than copying syntax from another implementation.
Query Safety and Resource Limits
If a user can provide a JSONPath expression, treat it as a small program:
- allow only approved path templates or a restricted grammar;
- reject script, function, callback, and mutation extensions;
- limit expression length, recursion depth, descendant traversal, candidate nodes, result count, and evaluation time;
- avoid fetching remote documents from inside a query;
- authorize the tenant, object, field, and purpose before evaluation;
- redact sensitive values and cap serialized result size.
Broad $..* queries over large documents can be expensive. A query engine should fail closed on timeouts and resource exhaustion, not return a partial result that callers mistake for a complete answer.
Querying APIs and Configuration
For API tests, use stable paths and assert both value and cardinality. A query such as $.users[*].email may pass while silently accepting an empty list or a wrong response shape; pair it with JSON Schema and business assertions.
For configuration, a JSONPath selector can locate a field, but it does not prove that changing it is allowed. Apply schema validation, environment policy, secret handling, and authorization before writing any result. Prefer a declarative patch format with a version precondition over a library mutation callback.
For analytics, materialize bounded projections rather than recursively scanning every request. Record the query dialect, library version, input revision, policy, result count, and failure state.
Common Mistakes
| Mistake | Why it fails | Better approach |
|---|---|---|
| Treating all JSONPath syntax as portable | Libraries implement different dialects | Pin a dialect and test fixtures |
Using $..* for every query |
Broad traversal can be expensive and ambiguous | Select a bounded subtree |
Calling update() on untrusted paths |
Query becomes a mutation and side-effect boundary | Use authorized, versioned patches |
| Assuming empty result means valid absence | Missing and null may have different meanings |
Define cardinality and schema assertions |
| Passing raw paths from users | Filter/script extensions can be abused | Allowlist templates and disable extensions |
| Using JSONPath for authorization | Selection does not prove ownership | Check tenant/object/field policy separately |
Frequently Asked Questions
Is JSONPath an official standard?
RFC 9535 defines a JSONPath standard, but older libraries and the original 2007 proposal have differing syntax and extensions. State the dialect and version in every shared query contract.
Does JSONPath replace loops?
It can express a selection compactly, but the engine still traverses data internally. For complex transformations, joins, aggregation, streaming, or explicit error handling, ordinary code or a different query language may be clearer and safer.
Can JSONPath modify JSON?
Some libraries expose update or mutation APIs, but mutation is not a portable JSONPath guarantee. Treat it as a separate operation with authorization, version checks, idempotency, and output validation.
Is a JSONPath filter safe for untrusted input?
Not by default. Filter syntax may include expression evaluation or extensions. Use an allowlist/restricted grammar, disable scripts and callbacks, and enforce resource budgets.
Should JSONPath results be used directly in responses?
Only after authorization, schema checks, redaction, cardinality checks, and output limits. A successful query does not prove that every matched field may be disclosed.
Primary Sources
- RFC 9535: JSONPath
- RFC 6901: JSON Pointer
- JSON Schema 2020-12
- JMESPath Specification
- jsonpath-plus documentation
- jsonpath-ng documentation
Conclusion
JSONPath is valuable when its dialect, result semantics, and resource limits are explicit. Use it for bounded read queries, keep validation and authorization separate, and treat extensions that execute code or mutate data as independent high-risk features. A short expression is useful only when its meaning remains portable, testable, and safe at the boundary where it runs.