Skip to content

Commit ac09c66

Browse files
[JsonPath] Fix subexpression evaluation in filters
1 parent cb08480 commit ac09c66

File tree

2 files changed

+95
-26
lines changed

2 files changed

+95
-26
lines changed

src/Symfony/Component/JsonPath/JsonCrawler.php

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -80,26 +80,31 @@ private function evaluate(JsonPath $query): array
8080
throw new InvalidJsonStringInputException($e->getMessage(), $e);
8181
}
8282

83-
$current = [$data];
84-
85-
foreach ($tokens as $token) {
86-
$next = [];
87-
foreach ($current as $value) {
88-
$result = $this->evaluateToken($token, $value);
89-
$next = array_merge($next, $result);
90-
}
91-
92-
$current = $next;
93-
}
94-
95-
return $current;
83+
return $this->evaluateTokensOnDecodedData($tokens, $data);
9684
} catch (InvalidArgumentException $e) {
9785
throw $e;
9886
} catch (\Throwable $e) {
9987
throw new JsonCrawlerException($query, $e->getMessage(), previous: $e);
10088
}
10189
}
10290

91+
private function evaluateTokensOnDecodedData(array $tokens, array $data): array
92+
{
93+
$current = [$data];
94+
95+
foreach ($tokens as $token) {
96+
$next = [];
97+
foreach ($current as $value) {
98+
$result = $this->evaluateToken($token, $value);
99+
$next = array_merge($next, $result);
100+
}
101+
102+
$current = $next;
103+
}
104+
105+
return $current;
106+
}
107+
103108
private function evaluateToken(JsonPathToken $token, mixed $value): array
104109
{
105110
return match ($token->type) {
@@ -246,10 +251,6 @@ private function evaluateFilter(string $expr, mixed $value): array
246251

247252
$result = [];
248253
foreach ($value as $item) {
249-
if (!\is_array($item)) {
250-
continue;
251-
}
252-
253254
if ($this->evaluateFilterExpression($expr, $item)) {
254255
$result[] = $item;
255256
}
@@ -258,7 +259,7 @@ private function evaluateFilter(string $expr, mixed $value): array
258259
return $result;
259260
}
260261

261-
private function evaluateFilterExpression(string $expr, array $context): bool
262+
private function evaluateFilterExpression(string $expr, mixed $context): bool
262263
{
263264
$expr = trim($expr);
264265

@@ -294,10 +295,12 @@ private function evaluateFilterExpression(string $expr, array $context): bool
294295
}
295296
}
296297

297-
if (str_starts_with($expr, '@.')) {
298-
$path = substr($expr, 2);
298+
if ('@' === $expr) {
299+
return true;
300+
}
299301

300-
return \array_key_exists($path, $context);
302+
if (str_starts_with($expr, '@.')) {
303+
return (bool) ($this->evaluateTokensOnDecodedData(JsonPathTokenizer::tokenize(new JsonPath('$'. substr($expr, 1))), $context)[0] ?? false);
301304
}
302305

303306
// function calls
@@ -315,12 +318,16 @@ private function evaluateFilterExpression(string $expr, array $context): bool
315318
return false;
316319
}
317320

318-
private function evaluateScalar(string $expr, array $context): mixed
321+
private function evaluateScalar(string $expr, mixed $context): mixed
319322
{
320323
if (is_numeric($expr)) {
321324
return str_contains($expr, '.') ? (float) $expr : (int) $expr;
322325
}
323326

327+
if ('@' === $expr) {
328+
return $context;
329+
}
330+
324331
if ('true' === $expr) {
325332
return true;
326333
}
@@ -339,10 +346,12 @@ private function evaluateScalar(string $expr, array $context): mixed
339346
}
340347

341348
// current node references
342-
if (str_starts_with($expr, '@.')) {
343-
$path = substr($expr, 2);
349+
if (str_starts_with($expr, '@')) {
350+
if (!\is_array($context)) {
351+
return null;
352+
}
344353

345-
return $context[$path] ?? null;
354+
return $this->evaluateTokensOnDecodedData(JsonPathTokenizer::tokenize(new JsonPath('$'. substr($expr, 1))), $context)[0] ?? null;
346355
}
347356

348357
// function calls

src/Symfony/Component/JsonPath/Tests/JsonCrawlerTest.php

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,14 @@ public function testBooksWithIsbn()
121121
], [$result[0]['isbn'], $result[1]['isbn']]);
122122
}
123123

124+
public function testBooksWithPublisherAddress()
125+
{
126+
$result = self::getBookstoreCrawler()->find('$..book[?(@.publisher.address)]');
127+
128+
$this->assertCount(1, $result);
129+
$this->assertSame('Sword of Honour', $result[0]['title']);
130+
}
131+
124132
public function testBooksLessThanTenDollars()
125133
{
126134
$result = self::getBookstoreCrawler()->find('$..book[?(@.price < 10)]');
@@ -344,6 +352,50 @@ public function testValueFunction()
344352
$this->assertSame('Sayings of the Century', $result[0]['title']);
345353
}
346354

355+
public function testDeepExpressionInFilter()
356+
{
357+
$result = self::getBookstoreCrawler()->find('$.store.book[?(@.publisher.address.city == "Springfield")]');
358+
359+
$this->assertCount(1, $result);
360+
$this->assertSame('Sword of Honour', $result[0]['title']);
361+
}
362+
363+
public function testWildcardInFilter()
364+
{
365+
$result = self::getBookstoreCrawler()->find('$.store.book[?(@.publisher.* == "my-publisher")]');
366+
367+
$this->assertCount(1, $result);
368+
$this->assertSame('Sword of Honour', $result[0]['title']);
369+
}
370+
371+
public function testWildcardInFunction()
372+
{
373+
$result = self::getBookstoreCrawler()->find('$.store.book[?match(@.publisher.*.city, "Spring.+")]');
374+
375+
$this->assertCount(1, $result);
376+
$this->assertSame('Sword of Honour', $result[0]['title']);
377+
}
378+
379+
public function testUseAtSymbolReturnsAll()
380+
{
381+
$result = self::getBookstoreCrawler()->find('$.store.bicycle[?(@ == @)]');
382+
383+
$this->assertSame([
384+
'red',
385+
399,
386+
], $result);
387+
}
388+
389+
public function testUseAtSymbolAloneReturnsAll()
390+
{
391+
$result = self::getBookstoreCrawler()->find('$.store.bicycle[?(@)]');
392+
393+
$this->assertSame([
394+
'red',
395+
399,
396+
], $result);
397+
}
398+
347399
public function testValueFunctionWithOuterParentheses()
348400
{
349401
$result = self::getBookstoreCrawler()->find('$.store.book[?(value(@.price) == 8.95)]');
@@ -420,7 +472,15 @@ private static function getBookstoreCrawler(): JsonCrawler
420472
"category": "fiction",
421473
"author": "Evelyn Waugh",
422474
"title": "Sword of Honour",
423-
"price": 12.99
475+
"price": 12.99,
476+
"publisher": {
477+
"name": "my-publisher",
478+
"address": {
479+
"street": "1234 Elm St",
480+
"city": "Springfield",
481+
"state": "IL"
482+
}
483+
}
424484
},
425485
{
426486
"category": "fiction",

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy