This is a JSONPath implementation for PHP with an opt-in RFC 9535 mode and a backward-compatible default mode. Both use cached queries and evaluate expressions without eval.
- PHP 8.3+ with typed class constants, enums, readonly tokens,
#[Override], and noeval; CI covers PHP 8.3 through the PHP 8.6 nightly. - Works with arrays, objects, and
ArrayAccess/traversables in any combination. - Supports child and descendant selectors, wildcards, quoted name unions, indexes, negative indexes, and array slices.
- Filters support existence tests (including descendant and nested filters), path-to-path/root comparisons, deep equality, and distinct RFC
Nothingversus JSONnullsemantics. - Malformed and non-portable legacy syntax is rejected instead of being interpreted as a different valid query.
- RFC mode passes all 715 cases in the pinned JSONPath compliance suite, including normalized result paths and rejection of invalid selectors.
Requires PHP 8.3 or newer.
composer require softcreatr/jsonpath:"^2.0"Useful commands:
composer exec phpunit
composer phpstan
composer csThe PHPUnit suite includes the pinned JSONPath Compliance Test Suite. Its 715 cases check both returned values and normalized paths, preserve objects versus arrays, and accept alternative node orderings only where the fixtures explicitly allow them. Tests run offline. CI verifies 100% class, method, and line coverage with Xdebug. Code style uses PHP CS Fixer.
Enable JSONPath::RFC9535 for standard query syntax and semantics. Existing calls without this flag keep their previous behavior.
use Flow\JSONPath\JSONPath;
$data = json_decode(
'{"books":[{"title":"First","price":5},{"title":"Second","price":15}]}',
associative: false,
flags: JSON_THROW_ON_ERROR,
);
$query = new JSONPath($data, JSONPath::RFC9535);
$query->find('$.books[?@.price < 10].title')->getData();
// ['First']
$query->findPaths('$.books[?@.price < 10].title');
// ["$['books'][0]['title']"]
$query->find('$.books[*,0].title')->getData();
// ['First', 'Second', 'First']Decode JSON objects as objects, as above. PHP lists represent JSON arrays; objects represent JSON objects. Associative PHP arrays, magic getters, and ArrayAccess belong to the compatibility API. RFC mode reads public object members and does not invoke magic accessors, even when ALLOW_MAGIC is also set.
RFC mode supports every selector kind in an ordered union, optional filter parentheses, Unicode member shorthands, JSON string escapes, negative and zero slice steps, logical precedence, and all five standard function extensions. Queries are parsed and type-checked before evaluation, including when the document is empty or a preceding segment has no matches. Invalid syntax raises JSONPathException.
The flag is retained by returned JSONPath objects. Compiled queries are cached separately for each mode. findPaths() always uses RFC mode and returns one normalized path per match in node-list order, including duplicates. It does not change the document or the values returned by find().
The default mode retains its existing extensions and restrictions, including rootless member queries, trimmed surrounding whitespace, trailing-comma unions, and the older filter parser. Choose RFC mode when queries must follow the standard; passing the community comparison suite alone does not establish RFC compliance.
| JSONPath | Result |
|---|---|
$.store.books[*].author |
the authors of all books in the store |
$..author |
all authors |
$.store..price |
the price of everything in the store. |
$..books[2] |
the third book |
$..books[-1:] |
the last book in order. |
$..books[0,1] |
the first two books |
$..books['title','year'] |
multiple keys in a union |
$..books[:2] |
the first two books |
$..books[::2] |
every second book starting from first one |
$..books[1:6:3] |
every third book starting from 1 till 6 |
$..books[?(@.isbn)] |
filter all books with isbn number |
$..books[?(@.price<10)] |
filter all books cheaper than 10 |
$..* |
all elements in the data (recursively extracted) |
| Symbol | Description |
|---|---|
$ |
The root object/element |
@ |
The current object/element inside a filter |
. or [] |
Child operator |
.. |
Recursive descent |
* |
Wildcard. All child elements regardless their index. |
[,] |
Array indexes or quoted member names as a set |
[start:end:step] |
Array slice operator borrowed from ES4/Python. |
?() |
Filters a node list by an existence or comparison expression |
<?php
require_once __DIR__ . '/vendor/autoload.php';
$data = ['people' => [
['name' => 'Sascha'],
['name' => 'Bianca'],
['name' => 'Alexander'],
['name' => 'Maximilian'],
]];
print_r((new \Flow\JSONPath\JSONPath($data))->find('$.people.*.name')->getData());
/*
Array
(
[0] => Sascha
[1] => Bianca
[2] => Alexander
[3] => Maximilian
)
*/<?php
require_once __DIR__ . '/vendor/autoload.php';
$data = json_decode('{"name":"Sascha Greuel","birthdate":"1987-12-16","city":"Gladbeck","country":"Germany"}', false);
print_r((new \Flow\JSONPath\JSONPath($data))->find('$')->getData()[0]);
/*
stdClass Object
(
[name] => Sascha Greuel
[birthdate] => 1987-12-16
[city] => Gladbeck
[country] => Germany
)
*/The options flag JSONPath::ALLOW_MAGIC will instruct JSONPath when retrieving a value to first check if an object
has a magic __get() method and will call this method if available. This feature is iffy and
not very predictable as:
- wildcard and recursive features will only look at public properties and can't smell which properties are magically accessible
- there is no
property_existscheck for magic methods so an object with a magic__get()will always returntruewhen checking if the property exists - any errors thrown or unpredictable behavior caused by fetching via
__get()is your own problem to deal with
<?php
use Flow\JSONPath\JSONPath;
$myObject = (new Foo())->get('bar');
$jsonPath = new JSONPath($myObject, JSONPath::ALLOW_MAGIC);Filter evaluation is intentionally limited to portable, eval-free operations:
- Comparisons support
==,!=,<,<=,>, and>=. - Logical expressions support
!,&&,||, and grouped subexpressions. - Singular current/root queries can be compared; non-singular, descendant, and nested-filter queries can be used as existence tests.
- Missing nodes use RFC
Nothingsemantics and are not conflated with JSONnull. - RFC 9535 function expressions are parsed, statically type-checked, and evaluated without
eval()or dynamic function dispatch.
Examples:
[?(@.title == "A string")] // equality
[?(@.price < 10)] // numeric comparison
[?(@.key == @.other)] // path-to-path comparison
[?(@.key == $.rootValue)] // root reference
[?(@.isbn)] // singular existence test
[?(@..child)] // descendant existence test
[?(@.items[?(@.price > 10)])] // nested-filter existence test
[?(@['weird-key']=="ok")] // bracket-escaped member
The following examples use this shared document:
$data = [
['name' => 'Ada', 'tags' => ['php', 'json'], 'timezone' => 'Europe/Berlin', 'colors' => ['red']],
['name' => 'Bob', 'tags' => ['php'], 'timezone' => 'America/Toronto', 'colors' => ['blue', 'green']],
['name' => '李', 'tags' => [], 'timezone' => 'Asia/Tokyo', 'colors' => []],
];Returns the number of Unicode scalar values in a string, elements in an array, or members in an object. Other values produce Nothing.
(new JSONPath($data))->find('$[?length(@.name) == 3].name')->getData();
// ['Ada', 'Bob']Returns the number of nodes selected by a query, including nodes whose value is null.
(new JSONPath($data))->find('$[?count(@.tags[*]) >= 2].name')->getData();
// ['Ada']Returns true when the entire input string matches an RFC 9485 I-Regexp.
(new JSONPath($data))->find('$[?match(@.timezone, "Europe/.*")].name')->getData();
// ['Ada']Returns true when any substring of the input string matches an RFC 9485 I-Regexp.
(new JSONPath($data))->find('$[?search(@.name, "ob")].name')->getData();
// ['Bob']Returns the value of a nodelist containing exactly one node. Empty or multi-node nodelists produce Nothing.
(new JSONPath($data))->find('$[?value(@.colors[*]) == "red"].name')->getData();
// ['Ada']Function calls are checked against the RFC ValueType, LogicalType, and NodesType rules before evaluation. Invalid I-Regexps evaluate to logical false.
Script expressions, synthetic .length, legacy regular-expression/membership operators, and array/object literals in comparisons are not supported. Unsupported or ill-typed syntax raises JSONPathException.
A full list of comparison cases can be found in the JSONPath Comparison Cheatsheet.
FlowCommunications/JSONPath is the predecessor of this library by Stephen Frank
Other / Similar implementations can be found in the Wiki.
A list of changes can be found in the CHANGELOG.md file.
This package is Treeware. If you use it in production, then we ask that you buy the world a tree to thank us for our work. By contributing to the ecologi project, you’ll be creating employment for local families and restoring wildlife habitats.
|
Sascha Greuel |
James Lucas |
Fabian Blechschmidt |
Mikko Pesari |
warlof |
Sergey G |
|
Alexandru Pătrănescu |
Oleg Andreyev |
Remy Suen |
esomething |