A secure, expression-evaluation-free JSONPath implementation for TypeScript/JavaScript with RFC 9535 compliance.
Live Demo | RFC 9535 Specification
- RFC 9535 compliant - Follows the official JSONPath specification
- Secure - No dynamic code execution, no injection vulnerabilities
- TypeScript native - Full type definitions included
- Dual module - Works with ESM and CommonJS
- Browser ready - Works in Node.js and browsers
npm install jsonpathlyimport { query, paths } from "jsonpathly";
const championship = {
name: "World Chess Championship 2023",
players: [
{ name: "Ding Liren", rating: 2788, country: "China" },
{ name: "Ian Nepomniachtchi", rating: 2795, country: "Russia" },
],
};
// Query values
query(championship, "$.players[*].name");
// => ["Ding Liren", "Ian Nepomniachtchi"]
// Get paths
paths(championship, "$.players[0]");
// => ["$['players'][0]"]
// Filter expressions
query(championship, "$.players[?@.rating > 2790].name");
// => ["Ian Nepomniachtchi"]JSONPath expressions start with $ (root) and can use dot or bracket notation:
$.players[0].name
$['players'][0]['name']
| Operator | Description |
|---|---|
$ |
Root element |
@ |
Current element in filter expressions |
* |
Wildcard - matches all elements |
.. |
Recursive descent |
.<name> |
Dot-notated child |
['<name>'] |
Bracket-notated child |
[<index>] |
Array index |
[start:end:step] |
Array slice |
[?<expression>] |
Filter expression |
[<expr>, <expr>] |
Union - multiple selectors |
| Operator | Description | Example |
|---|---|---|
== |
Equal | $[?@.title == 'Grandmaster'] |
!= |
Not equal | $[?@.country != 'Russia'] |
< |
Less than | $[?@.rating < 2700] |
<= |
Less than or equal | $[?@.rating <= 2700] |
> |
Greater than | $[?@.rating > 2800] |
>= |
Greater than or equal | $[?@.rating >= 2800] |
&& |
Logical AND | $[?@.rating > 2700 && @.active] |
|| |
Logical OR | $[?@.champion || @.challenger] |
! |
Logical NOT | $[?!@.retired] |
=~ |
Regex match | $[?@.name =~ /^Magnus/i] |
| Function | Description | Example |
|---|---|---|
length() |
Length of string, array, or object | $[?length(@.name) > 10] |
count() |
Count of elements in a nodelist | $[?count(@.titles[*]) > 5] |
match() |
Full string regex match | $[?match(@.title, '^GM.*')] |
search() |
Regex search within string | $[?search(@.name, 'Kasparov')] |
value() |
Extract value from nodelist | $[?value(@.achievements[0]) == 'World Champion'] |
| Operator | Description | Example |
|---|---|---|
in |
Value exists in array | $[?@.federation in ['FIDE', 'USCF']] |
nin |
Value not in array | $[?@.result nin ['loss']] |
subsetof |
Left is subset of right | $[?@.titles subsetof ['GM', 'IM', 'FM']] |
anyof |
Any element in common | $[?@.styles anyof ['positional', 'tactical']] |
noneof |
No elements in common | $[?@.weaknesses noneof ['time trouble']] |
size |
Array/string length equals | $[?@.championships size 5] |
empty |
Array/string is empty | $[?@.losses empty] |
Query JSON data and return matching values.
import { query } from "jsonpathly";
const legends = {
champions: [
{ name: "Garry Kasparov", era: "1985-2000", rating: 2851 },
{ name: "Magnus Carlsen", era: "2013-2023", rating: 2882 },
],
};
query(legends, "$.champions[*].name");
// => ["Garry Kasparov", "Magnus Carlsen"]
query(legends, "$.champions[0].era");
// => "1985-2000"Options:
| Option | Type | Description |
|---|---|---|
hideExceptions |
boolean |
Return undefined instead of throwing (or [] if returnArray is true) |
returnArray |
boolean |
Always return results as an array |
Get normalized paths to matching elements.
import { paths } from "jsonpathly";
const tournament = {
candidates: [
{ player: "Bobby Fischer", year: 1971 },
{ player: "Anatoly Karpov", year: 1974 },
],
};
paths(tournament, "$..player");
// => ["$['candidates'][0]['player']", "$['candidates'][1]['player']"]Options:
| Option | Type | Description |
|---|---|---|
hideExceptions |
boolean |
Return empty array instead of throwing |
Parse a JSONPath expression into an AST.
import { parse } from "jsonpathly";
parse("$.players[0].rating");
// => { type: 'root', next: { type: 'subscript', ... } }Convert an AST back to a JSONPath string.
import { parse, stringify } from "jsonpathly";
stringify(parse("$.champions[*].name"));
// => "$.champions[*].name"Custom error class for syntax errors.
import { query, JSONPathSyntaxError } from "jsonpathly";
try {
query({}, "$[invalid");
} catch (e) {
if (e instanceof JSONPathSyntaxError) {
console.log("Invalid JSONPath:", e.message);
}
}jsonpathly is designed with security in mind:
- No dynamic code execution - Expressions are parsed into an AST and evaluated safely
- No code injection - Script expressions
$(...)are not supported - I-Regexp compliance - Regex patterns in
match()andsearch()are validated against RFC 9485
For a comparison with other JSONPath implementations, see json-path-comparison.
Key differences:
- Uses a Peggy parser instead of dynamic execution
- Follows RFC 9535 semantics for edge cases
- Normalized paths use single quotes per RFC 9535
- Path format changed:
paths()now returns RFC 9535 normalized format with single quotes:Before: $["players"][0]["name"] After: $['players'][0]['name'] - Node.js 18+ required
- Node.js >= 18
MIT
