jsonpath
jsonpath is a CommonJS query and mutation utility for JavaScript object graphs. It implements the original Goessner-style JSONPath notation, including child and recursive descent, wildcards, unions, array slices, filters, and script subscripts. Beyond returning matching values, it can return concrete key paths, pair paths with values, read or set the first match, find a parent, transform every match in place, and parse or stringify path expressions.
A useful compatibility choice for existing Goessner-style paths, especially when path arrays and mutations matter. Start new cross-language systems with a standards-focused engine, and do not run jsonpath versions older than 1.3.0.
Use it if
- You maintain a Node.js codebase that already uses Goessner-style JSONPath expressions and needs a current security-fixed release
- You need both matched values and concrete key-array paths for diagnostics, editors, or targeted updates
- You want controlled filter expressions without exposing the full JavaScript runtime to the expression
- You need built-in mutation helpers such as value and apply in addition to read-only queries
- You need portable RFC 9535 behavior across languages: the README targets Stefan Goessner's 2007 design and explicitly documents implementation-specific grammar and evaluation differences
- Your expressions call functions, use external variables, or run arbitrary JavaScript: filters are statically evaluated with scope limited to @, and current hardening rejects calls, constructors, assignments, templates, and unsafe property names
- You want TypeScript declarations or an ESM-first API in the package: 1.3.0 ships neither a types entry nor an ESM export, so TypeScript relies on the separate @types/jsonpath package
- You process huge or adversarial object graphs: query, paths, and nodes return materialized arrays, recursive descent can inspect every nested member, and there is no streaming iterator
- You cannot upgrade old transitive copies promptly: 1.2.0 added prototype-pollution defenses and 1.3.0 followed with a fix for CVE-2026-1615, so older 1.x versions should not be treated as equivalent
Setup reality
`npm install jsonpath` gives you a CommonJS module with three runtime dependencies: the old Esprima 1.2.5 parser, static-eval 2.1.1, and Underscore 1.13.6. There are no peer dependencies or native builds. Version 1.3.0 publishes a browser field, but it does not expose an ESM entry or bundled TypeScript declarations; add `@types/jsonpath` if your compiler needs types and verify that its declarations cover the newest security behavior. The main compatibility decision is syntax. This package follows the original Goessner family rather than promising RFC 9535 conformance, and its formal grammar is intentionally stricter in places. Non-ASCII or punctuation-heavy property names must use quoted bracket notation. Filter and script expressions are parsed and statically evaluated with only `@` in scope; ordinary comparisons and boolean logic work, while method calls, global access, assignments, constructors, and other executable constructs are rejected. That is safer than eval, but it also breaks expressions copied from permissive engines. Pin at least 1.3.0: the 2026 releases added unsafe-key blocks for `__proto__`, `prototype`, and `constructor`, then tightened expression evaluation for CVE-2026-1615. Read methods return arrays, even for one expected result, except `value`, `parent`, and mutations with their own first-match behavior. The optional count argument caps results and can prevent unnecessary traversal. Mutation is easy to overlook: `value(obj, path, newValue)` changes the first match and can create a missing simple path, while `apply` replaces every match in the original object. Clone first if immutable inputs matter, and never accept unrestricted expressions merely because static evaluation is narrower than JavaScript.
Patterns
Return all matching valuesquery-values
const jp = require('jsonpath');
const authors = jp.query(data, '$.store.book[*].author');query always returns an array. No matches produce an empty array rather than undefined.
Find a property at any depthrecursive-descent
const prices = jp.query(data, '$..price');Recursive descent scans the nested graph and can return prices from unrelated branches. Prefer a narrower path when structure is known.
Filter objects with comparisons and boolean logicfilter-array
const books = jp.query(
data,
'$.store.book[?(@.price < 30 && @.category == "fiction")]'
);Filters use a restricted static evaluator with only @ in scope. Function calls and access to process or other globals are rejected.
Stop after a fixed number of matcheslimit-results
const firstTwoAuthors = jp.query(data, '$..author', 2);
const none = jp.query(data, '$..author', 0);The third argument limits returned matches; zero returns an empty array without traversing.
Select a range or the last itemslice-array
const firstTwo = jp.query(data, '$.store.book[:2]');
const last = jp.query(data, '$.store.book[-1:]');
const reversed = jp.query(data, '$.store.book[::-1]');Slice syntax follows Python-like start:end:step behavior. String values are not sliced as character arrays.
Select several fields or indicesselect-union
const twoBooks = jp.query(data, '$.store.book[0,2]');
const fields = jp.query(data, '$.store.bicycle["color","price"]');Unions remove duplicate results rather than concatenating duplicates, one documented difference from some older implementations.
Query keys that are not identifiersquote-property-name
const value = jp.query(record, '$["display-name"]');
const currency = jp.query(record, '$["€"]');Use quoted bracket notation for punctuation or non-ASCII keys. Unsafe keys such as __proto__, constructor, and prototype are rejected in 1.3.0.
Return concrete paths instead of valuesget-match-paths
const paths = jp.paths(data, '$..author');
// Example: ['$', 'store', 'book', 0, 'author']Each result is a key array rooted with $. Keep array indices as numbers if you later pass the path back to value or stringify.
Pair each value with its concrete pathget-nodes
const nodes = jp.nodes(data, '$.store.book[*].price');
for (const { path, value } of nodes) {
console.log(jp.stringify(path), value);
}nodes is useful when duplicate values must still be distinguished by location. The returned value references the original object graph.
Read or replace the first matching valueread-write-value
const oldPrice = jp.value(data, '$.store.book[0].price');
const newPrice = jp.value(data, '$.store.book[0].price', 9.5);The three-argument form mutates the original object and only changes the first match. For a missing simple path, it can create intermediate objects or arrays.
Replace every matching valuetransform-matches
const changed = jp.apply(data, '$.store.book[*].price', (price) => {
return Math.round(price * 100);
});apply mutates data in place and returns nodes containing updated values. Clone the object first when callers expect immutability.
Inspect and rebuild a path expressionparse-stringify-path
const parsed = jp.parse('$..author');
const expression = jp.stringify(parsed);
const concrete = jp.stringify(['$', 'store', 'book', 0, 'author']);stringify accepts either parsed components or a concrete key array. It is useful for logging paths returned by paths or nodes.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| jsonpath-plus | npm | You need a more feature-rich JSONPath implementation with modern module support and configurable result types |
| jmespath | npm | You want a cross-language query language focused on projections and transformations rather than in-place mutation |
| jsonata | npm | You need a richer expression language for querying, calculation, aggregation, and reshaping JSON data |