Skip to content

Commit baaf33e

Browse files
committed
feature #21078 [JsonPath] Add documentation (vinceAmstoutz)
This PR was merged into the 7.3 branch. Discussion ---------- [JsonPath] Add documentation Fixes #20977 by replacing the work started in PR #20974 by `@RisingSunLight42` Commits ------- abc2df6 docs(json-path): initiate doc for the component
2 parents 31d7c36 + abc2df6 commit baaf33e

File tree

1 file changed

+246
-0
lines changed

1 file changed

+246
-0
lines changed

components/json_path.rst

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
The JsonPath Component
2+
======================
3+
4+
.. versionadded:: 7.3
5+
6+
The JsonPath component was introduced in Symfony 7.3 as an
7+
:doc:`experimental feature </contributing/code/experimental>`.
8+
9+
The JsonPath component provides a powerful way to query and extract data from
10+
JSON structures. It implements the `RFC 9535 - JSONPath`_
11+
standard, allowing you to navigate complex JSON data with ease.
12+
13+
Just as the :doc:`DomCrawler component </components/dom_crawler>` allows you to navigate and query HTML/XML documents
14+
using XPath, the JsonPath component provides a similar experience to traverse and search JSON structures
15+
using JSONPath expressions. The component also offers an abstraction layer for data extraction.
16+
17+
Installation
18+
------------
19+
20+
You can install the component in your project using Composer:
21+
22+
.. code-block:: terminal
23+
24+
$ composer require symfony/json-path
25+
26+
.. include:: /components/require_autoload.rst.inc
27+
28+
Usage
29+
-----
30+
31+
To start querying a JSON document, first create a :class:`Symfony\\Component\\JsonPath\\JsonCrawler`
32+
object from a JSON string. For the following examples, we'll use this sample
33+
"bookstore" JSON data::
34+
35+
use Symfony\Component\JsonPath\JsonCrawler;
36+
37+
$json = <<<'JSON'
38+
{
39+
"store": {
40+
"book": [
41+
{
42+
"category": "reference",
43+
"author": "Nigel Rees",
44+
"title": "Sayings of the Century",
45+
"price": 8.95
46+
},
47+
{
48+
"category": "fiction",
49+
"author": "Evelyn Waugh",
50+
"title": "Sword of Honour",
51+
"price": 12.99
52+
},
53+
{
54+
"category": "fiction",
55+
"author": "Herman Melville",
56+
"title": "Moby Dick",
57+
"isbn": "0-553-21311-3",
58+
"price": 8.99
59+
},
60+
{
61+
"category": "fiction",
62+
"author": "John Ronald Reuel Tolkien",
63+
"title": "The Lord of the Rings",
64+
"isbn": "0-395-19395-8",
65+
"price": 22.99
66+
}
67+
],
68+
"bicycle": {
69+
"color": "red",
70+
"price": 399
71+
}
72+
}
73+
}
74+
JSON;
75+
76+
$crawler = new JsonCrawler($json);
77+
78+
Once you have the crawler instance, use its :method:`Symfony\\Component\\JsonPath\\JsonCrawler::find` method to start querying
79+
the data. This method always returns an array of matching values.
80+
81+
Querying with Expressions
82+
-------------------------
83+
84+
The primary way to query the JSON is by passing a JSONPath expression string
85+
to the :method:`Symfony\\Component\\JsonPath\\JsonCrawler::find` method.
86+
87+
Accessing a Specific Property
88+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
89+
90+
Use dot-notation for object keys and square brackets for array indices. The root
91+
of the document is represented by ``$``::
92+
93+
// Get the title of the first book in the store
94+
$titles = $crawler->find('$.store.book[0].title');
95+
96+
// $titles is ['Sayings of the Century']
97+
98+
While dot-notation is common, JSONPath offers alternative syntaxes which can be more flexible. For instance, bracket notation (`['...']`) is required if a key contains spaces or special characters::
99+
100+
// This is equivalent to the previous example and works with special keys
101+
$titles = $crawler->find('$["store"]["book"][0]["title"]');
102+
103+
// You can also build the query programmatically for more complex scenarios
104+
use Symfony\Component\JsonPath\JsonPath;
105+
106+
$path = (new JsonPath())->key('store')->key('book')->first()->key('title');
107+
$titles = $crawler->find($path);
108+
109+
Searching with the Descendant Operator
110+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
111+
112+
The descendant operator (``..``) recursively searches for a given key, allowing
113+
you to find values without specifying the full path::
114+
115+
// Get all authors from anywhere in the document
116+
$authors = $crawler->find('$..author');
117+
118+
// $authors is ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'John Ronald Reuel Tolkien']
119+
120+
Filtering Results
121+
~~~~~~~~~~~~~~~~~
122+
123+
JSONPath includes a powerful filter syntax (``?(<expression>)``) to select items
124+
based on a condition. The current item within the filter is referenced by ``@``::
125+
126+
// Get all books with a price less than 10
127+
$cheapBooks = $crawler->find('$.store.book[?(@.price < 10)]');
128+
129+
/*
130+
$cheapBooks contains two book objects:
131+
the one by "Nigel Rees" and the one by "Herman Melville"
132+
*/
133+
134+
Building Queries Programmatically
135+
---------------------------------
136+
137+
For more dynamic or complex query building, you can use the fluent API provided
138+
by the :class:`Symfony\\Component\\JsonPath\\JsonPath` class. This allows you
139+
to construct a query object step-by-step. The ``JsonPath`` object can then be
140+
passed to the crawler's :method:`Symfony\\Component\\JsonPath\\JsonCrawler::find` method.
141+
142+
The main advantage of the programmatic builder is that it automatically handles
143+
the correct escaping of keys and values, preventing syntax errors::
144+
145+
use Symfony\Component\JsonPath\JsonPath;
146+
147+
$path = (new JsonPath())
148+
->key('store') // Selects the 'store' key
149+
->key('book') // Then the 'book' key
150+
->index(1); // Then the item at index 1 (the second book)
151+
152+
// The created $path object is equivalent to the string '$["store"]["book"][1]'
153+
$book = $crawler->find($path);
154+
155+
// $book contains the book object for "Sword of Honour"
156+
157+
The :class:`Symfony\\Component\\JsonPath\\JsonPath` class provides several methods to build your query:
158+
159+
* :method:`Symfony\\Component\\JsonPath\\JsonPath::key`
160+
Adds a key selector. The key name will be properly escaped::
161+
162+
// Creates the path '$["key\"with\"quotes"]'
163+
$path = (new JsonPath())->key('key"with"quotes');
164+
165+
* :method:`Symfony\\Component\\JsonPath\\JsonPath::deepScan`
166+
Adds the descendant operator ``..`` to perform a recursive search from the
167+
current point in the path::
168+
169+
// Get all prices in the store: '$["store"]..["price"]'
170+
$path = (new JsonPath())->key('store')->deepScan()->key('price');
171+
172+
* :method:`Symfony\\Component\\JsonPath\\JsonPath::all`
173+
Adds the wildcard operator ``[*]`` to select all items in an array or object::
174+
175+
// Creates the path '$["store"]["book"][*]'
176+
$path = (new JsonPath())->key('store')->key('book')->all();
177+
178+
* :method:`Symfony\\Component\\JsonPath\\JsonPath::index`
179+
Adds an array index selector.
180+
181+
* :method:`Symfony\\Component\\JsonPath\\JsonPath::first` / :method:`Symfony\\Component\\JsonPath\\JsonPath::last`
182+
Shortcuts for ``index(0)`` and ``index(-1)`` respectively::
183+
184+
// Get the last book: '$["store"]["book"][-1]'
185+
$path = (new JsonPath())->key('store')->key('book')->last();
186+
187+
* :method:`Symfony\\Component\\JsonPath\\JsonPath::slice`
188+
Adds an array slice selector ``[start:end:step]``::
189+
190+
// Get books from index 1 up to (but not including) index 3
191+
// Creates the path '$["store"]["book"][1:3]'
192+
$path = (new JsonPath())->key('store')->key('book')->slice(1, 3);
193+
194+
// Get every second book from the first four books
195+
// Creates the path '$["store"]["book"][0:4:2]'
196+
$path = (new JsonPath())->key('store')->key('book')->slice(0, 4, 2);
197+
198+
* :method:`Symfony\\Component\\JsonPath\\JsonPath::filter`
199+
Adds a filter expression. The expression string is the part that goes inside
200+
the ``?()`` syntax::
201+
202+
// Get expensive books: '$["store"]["book"][?(@.price > 20)]'
203+
$path = (new JsonPath())
204+
->key('store')
205+
->key('book')
206+
->filter('@.price > 20');
207+
208+
Advanced Querying
209+
-----------------
210+
211+
For a complete overview of advanced operators like wildcards and functions within
212+
filters, please refer to the `Querying with Expressions`_ section above. All these
213+
features are supported and can be combined with the programmatic builder where
214+
appropriate (e.g., inside a ``filter()`` expression).
215+
216+
Error Handling
217+
--------------
218+
219+
The component will throw specific exceptions for invalid input or queries:
220+
221+
* :class:`Symfony\\Component\\JsonPath\\Exception\\InvalidArgumentException`: Thrown if the input to the ``JsonCrawler`` constructor is not a valid JSON string.
222+
* :class:`Symfony\\Component\\JsonPath\\Exception\\InvalidJsonStringInputException`: Thrown during a ``find()`` call if the JSON string is malformed (e.g., syntax error).
223+
* :class:`Symfony\\Component\\JsonPath\\Exception\\JsonCrawlerException`: Thrown for errors within the JsonPath expression itself, such as using an unknown function::
224+
225+
use Symfony\Component\JsonPath\Exception\InvalidJsonStringInputException;
226+
use Symfony\Component\JsonPath\Exception\JsonCrawlerException;
227+
228+
try {
229+
// Example of malformed JSON
230+
$crawler = new JsonCrawler('{"store": }');
231+
$crawler->find('$..*');
232+
} catch (InvalidJsonStringInputException $e) {
233+
// ... handle error
234+
}
235+
236+
try {
237+
// Example of an invalid query
238+
$crawler->find('$.store.book[?unknown_function(@.price)]');
239+
} catch (JsonCrawlerException $e) {
240+
// ... handle error
241+
}
242+
243+
Learn more
244+
----------
245+
246+
.. _`RFC 9535 - JSONPath`: https://datatracker.ietf.org/doc/html/rfc9535

0 commit comments

Comments
 (0)