|
| 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) <https://datatracker.ietf.org/doc/html/rfc9535>`_ |
| 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 | +Searching with the Descendant Operator |
| 99 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 100 | + |
| 101 | +The descendant operator (``..``) recursively searches for a given key, allowing |
| 102 | +you to find values without specifying the full path. |
| 103 | + |
| 104 | +:: |
| 105 | + |
| 106 | + // Get all authors from anywhere in the document |
| 107 | + $authors = $crawler->find('$..author'); |
| 108 | + |
| 109 | + // $authors is ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'John Ronald Reuel Tolkien'] |
| 110 | + |
| 111 | +Filtering Results |
| 112 | +~~~~~~~~~~~~~~~~~ |
| 113 | + |
| 114 | +JSONPath includes a powerful filter syntax (``?(<expression>)``) to select items |
| 115 | +based on a condition. The current item within the filter is referenced by ``@``. |
| 116 | + |
| 117 | +:: |
| 118 | + |
| 119 | + // Get all books with a price less than 10 |
| 120 | + $cheapBooks = $crawler->find('$.store.book[?(@.price < 10)]'); |
| 121 | + |
| 122 | + /* |
| 123 | + $cheapBooks contains two book objects: |
| 124 | + the one by "Nigel Rees" and the one by "Herman Melville" |
| 125 | + */ |
| 126 | + |
| 127 | +Building Queries Programmatically |
| 128 | +--------------------------------- |
| 129 | + |
| 130 | +For more dynamic or complex query building, you can use the fluent API provided |
| 131 | +by the :class:`Symfony\\Component\\JsonPath\\JsonPath` class. This allows you |
| 132 | +to construct a query object step-by-step. The ``JsonPath`` object can then be |
| 133 | +passed to the crawler's method:`Symfony\\Component\\JsonPath\\JsonCrawler::find` method. |
| 134 | + |
| 135 | +The main advantage of the programmatic builder is that it automatically handles |
| 136 | +the correct escaping of keys and values, preventing syntax errors. |
| 137 | + |
| 138 | +:: |
| 139 | + |
| 140 | + use Symfony\Component\JsonPath\JsonPath; |
| 141 | + |
| 142 | + $path = (new JsonPath()) |
| 143 | + ->key('store') // Selects the 'store' key |
| 144 | + ->key('book') // Then the 'book' key |
| 145 | + ->index(1); // Then the item at index 1 (the second book) |
| 146 | + |
| 147 | + // The created $path object is equivalent to the string '$["store"]["book"][1]' |
| 148 | + $book = $crawler->find($path); |
| 149 | + |
| 150 | + // $book contains the book object for "Sword of Honour" |
| 151 | + |
| 152 | +The ``JsonPath`` class provides several methods to build your query: |
| 153 | + |
| 154 | +``key(string $name)`` |
| 155 | + Adds a key selector. The key name will be properly escaped. |
| 156 | + |
| 157 | + :: |
| 158 | + |
| 159 | + // Creates the path '$["key\"with\"quotes"]' |
| 160 | + $path = (new JsonPath())->key('key"with"quotes'); |
| 161 | + |
| 162 | +``deepScan()`` |
| 163 | + Adds the descendant operator ``..`` to perform a recursive search from the |
| 164 | + current point in the path. |
| 165 | + |
| 166 | + :: |
| 167 | + |
| 168 | + // Get all prices in the store: '$["store"]..["price"]' |
| 169 | + $path = (new JsonPath())->key('store')->deepScan()->key('price'); |
| 170 | + |
| 171 | +``all()`` |
| 172 | + Adds the wildcard operator ``[*]`` to select all items in an array or object. |
| 173 | + |
| 174 | + :: |
| 175 | + |
| 176 | + // Creates the path '$["store"]["book"][*]' |
| 177 | + $path = (new JsonPath())->key('store')->key('book')->all(); |
| 178 | + |
| 179 | +``index(int $index)`` |
| 180 | + Adds an array index selector. |
| 181 | + |
| 182 | +``first()`` / ``last()`` |
| 183 | + Shortcuts for ``index(0)`` and ``index(-1)`` respectively. |
| 184 | + |
| 185 | + :: |
| 186 | + |
| 187 | + // Get the last book: '$["store"]["book"][-1]' |
| 188 | + $path = (new JsonPath())->key('store')->key('book')->last(); |
| 189 | + |
| 190 | +``slice(int $start, ?int $end = null, ?int $step = null)`` |
| 191 | + Adds an array slice selector ``[start:end:step]``. |
| 192 | + |
| 193 | + :: |
| 194 | + |
| 195 | + // Get books from index 1 up to (but not including) index 3 |
| 196 | + // Creates the path '$["store"]["book"][1:3]' |
| 197 | + $path = (new JsonPath())->key('store')->key('book')->slice(1, 3); |
| 198 | + |
| 199 | + // Get every second book from the first four books |
| 200 | + // Creates the path '$["store"]["book"][0:4:2]' |
| 201 | + $path = (new JsonPath())->key('store')->key('book')->slice(0, 4, 2); |
| 202 | + |
| 203 | +``filter(string $expression)`` |
| 204 | + Adds a filter expression. The expression string is the part that goes inside |
| 205 | + the ``?()`` syntax. |
| 206 | + |
| 207 | + :: |
| 208 | + |
| 209 | + // Get expensive books: '$["store"]["book"][?(@.price > 20)]' |
| 210 | + $path = (new JsonPath()) |
| 211 | + ->key('store') |
| 212 | + ->key('book') |
| 213 | + ->filter('@.price > 20'); |
| 214 | + |
| 215 | +Advanced Querying |
| 216 | +----------------- |
| 217 | + |
| 218 | +For a complete overview of advanced operators like wildcards and functions within |
| 219 | +filters, please refer to the `Querying with Expressions`_ section above. All these |
| 220 | +features are supported and can be combined with the programmatic builder where |
| 221 | +appropriate (e.g., inside a ``filter()`` expression). |
| 222 | + |
| 223 | +Error Handling |
| 224 | +-------------- |
| 225 | + |
| 226 | +The component will throw specific exceptions for invalid input or queries: |
| 227 | + |
| 228 | +* :class:`Symfony\\Component\\JsonPath\\Exception\\InvalidArgumentException`: Thrown if the input to the ``JsonCrawler`` constructor is not a valid JSON string. |
| 229 | +* :class:`Symfony\\Component\\JsonPath\\Exception\\InvalidJsonStringInputException`: Thrown during a ``find()`` call if the JSON string is malformed (e.g., syntax error). |
| 230 | +* :class:`Symfony\\Component\\JsonPath\\Exception\\JsonCrawlerException`: Thrown for errors within the JsonPath expression itself, such as using an unknown function. |
| 231 | + |
| 232 | +:: |
| 233 | + |
| 234 | + use Symfony\Component\JsonPath\Exception\InvalidJsonStringInputException; |
| 235 | + use Symfony\Component\JsonPath\Exception\JsonCrawlerException; |
| 236 | + |
| 237 | + try { |
| 238 | + // Example of malformed JSON |
| 239 | + $crawler = new JsonCrawler('{"store": }'); |
| 240 | + $crawler->find('$..*'); |
| 241 | + } catch (InvalidJsonStringInputException $e) { |
| 242 | + // ... handle error |
| 243 | + } |
| 244 | + |
| 245 | + try { |
| 246 | + // Example of an invalid query |
| 247 | + $crawler->find('$.store.book[?unknown_function(@.price)]'); |
| 248 | + } catch (JsonCrawlerException $e) { |
| 249 | + // ... handle error |
| 250 | + } |
0 commit comments