diff --git a/src/Symfony/Component/JsonPath/JsonPath.php b/src/Symfony/Component/JsonPath/JsonPath.php index 1009369b0a56d..e716167eb3f64 100644 --- a/src/Symfony/Component/JsonPath/JsonPath.php +++ b/src/Symfony/Component/JsonPath/JsonPath.php @@ -30,7 +30,9 @@ public function __construct( public function key(string $key): static { - return new self($this->path.(str_ends_with($this->path, '..') ? '' : '.').$key); + $escaped = $this->escapeKey($key); + + return new self($this->path.'["'.$escaped.'"]'); } public function index(int $index): static @@ -80,4 +82,25 @@ public function __toString(): string { return $this->path; } + + private function escapeKey(string $key): string + { + $key = strtr($key, [ + '\\' => '\\\\', + '"' => '\\"', + "\n" => '\\n', + "\r" => '\\r', + "\t" => '\\t', + "\b" => '\\b', + "\f" => '\\f' + ]); + + for ($i = 0; $i <= 31; $i++) { + if ($i < 8 || $i > 13) { + $key = str_replace(chr($i), sprintf('\\u%04x', $i), $key); + } + } + + return $key; + } } diff --git a/src/Symfony/Component/JsonPath/Tests/JsonCrawlerTest.php b/src/Symfony/Component/JsonPath/Tests/JsonCrawlerTest.php index 6871a56511890..66ccfc2642141 100644 --- a/src/Symfony/Component/JsonPath/Tests/JsonCrawlerTest.php +++ b/src/Symfony/Component/JsonPath/Tests/JsonCrawlerTest.php @@ -49,6 +49,19 @@ public function testAllAuthors() ], $result); } + public function testAllAuthorsWithBrackets() + { + $result = self::getBookstoreCrawler()->find('$..["author"]'); + + $this->assertCount(4, $result); + $this->assertSame([ + 'Nigel Rees', + 'Evelyn Waugh', + 'Herman Melville', + 'J. R. R. Tolkien', + ], $result); + } + public function testAllThingsInStore() { $result = self::getBookstoreCrawler()->find('$.store.*'); @@ -58,6 +71,15 @@ public function testAllThingsInStore() $this->assertArrayHasKey('color', $result[1]); } + public function testAllThingsInStoreWithBrackets() + { + $result = self::getBookstoreCrawler()->find('$["store"][*]'); + + $this->assertCount(2, $result); + $this->assertCount(4, $result[0]); + $this->assertArrayHasKey('color', $result[1]); + } + public function testEscapedDoubleQuotesInFieldName() { $crawler = new JsonCrawler(<<assertSame('Nigel Rees', $result[0]['author']); } + public function testBasicNameSelectorWithBrackts() + { + $result = self::getBookstoreCrawler()->find('$["store"]["book"]')[0]; + + $this->assertCount(4, $result); + $this->assertSame('Nigel Rees', $result[0]['author']); + } + public function testAllPrices() { $result = self::getBookstoreCrawler()->find('$.store..price'); @@ -121,6 +151,17 @@ public function testBooksWithIsbn() ], [$result[0]['isbn'], $result[1]['isbn']]); } + public function testBooksWithBracketsAndFilter() + { + $result = self::getBookstoreCrawler()->find('$..["book"][?(@.isbn)]'); + + $this->assertCount(2, $result); + $this->assertSame([ + '0-553-21311-3', + '0-395-19395-8', + ], [$result[0]['isbn'], $result[1]['isbn']]); + } + public function testBooksLessThanTenDollars() { $result = self::getBookstoreCrawler()->find('$..book[?(@.price < 10)]'); @@ -216,6 +257,14 @@ public function testEverySecondElementReverseSlice() $this->assertSame([6, 2, 5], $result); } + public function testEverySecondElementReverseSliceAndBrackets() + { + $crawler = self::getSimpleCollectionCrawler(); + + $result = $crawler->find('$["a"][::-2]'); + $this->assertSame([6, 2, 5], $result); + } + public function testEmptyResults() { $crawler = self::getSimpleCollectionCrawler(); @@ -404,6 +453,19 @@ public function testAcceptsJsonPath() $this->assertSame('red', $result[0]['color']); } + public function testStarAsKey() + { + $crawler = new JsonCrawler(<<find('$["*"]'); + + $this->assertCount(1, $result); + $this->assertSame(['a' => 1, 'b' => 2], $result[0]); + } + + private static function getBookstoreCrawler(): JsonCrawler { return new JsonCrawler(<<index(0) ->key('address'); - $this->assertSame('$.users[0].address', (string) $path); - $this->assertSame('$.users[0].address..city', (string) $path->deepScan()->key('city')); + $this->assertSame('$["users"][0]["address"]', (string) $path); + $this->assertSame('$["users"][0]["address"]..["city"]', (string) $path->deepScan()->key('city')); } public function testBuildWithFilter() @@ -33,7 +33,7 @@ public function testBuildWithFilter() $path = $path->key('users') ->filter('@.age > 18'); - $this->assertSame('$.users[?(@.age > 18)]', (string) $path); + $this->assertSame('$["users"][?(@.age > 18)]', (string) $path); } public function testAll() @@ -42,7 +42,7 @@ public function testAll() $path = $path->key('users') ->all(); - $this->assertSame('$.users[*]', (string) $path); + $this->assertSame('$["users"][*]', (string) $path); } public function testFirst() @@ -51,7 +51,7 @@ public function testFirst() $path = $path->key('users') ->first(); - $this->assertSame('$.users[0]', (string) $path); + $this->assertSame('$["users"][0]', (string) $path); } public function testLast() @@ -60,6 +60,47 @@ public function testLast() $path = $path->key('users') ->last(); - $this->assertSame('$.users[-1]', (string) $path); + $this->assertSame('$["users"][-1]', (string) $path); + } + + /** + * @dataProvider provideKeysToEscape + */ + public function testEscapedKey(string $key, string $expectedPath) + { + $path = new JsonPath(); + $path = $path->key($key); + + $this->assertSame($expectedPath, (string) $path); + } + + public static function provideKeysToEscape(): iterable + { + yield ['simple_key', '$["simple_key"]']; + yield ['key"with"quotes', '$["key\\"with\\"quotes"]']; + yield ['path\\backslash', '$["path\\backslash"]']; + yield ['mixed\\"case', '$["mixed\\\\\\"case"]']; + yield ['unicode_πŸ”‘', '$["unicode_πŸ”‘"]']; + yield ['"quotes_only"', '$["\\"quotes_only\\""]']; + yield ['\\\\multiple\\\\backslashes', '$["\\\\\\\\multiple\\\\\\backslashes"]']; + yield ["control\x00\x1f\x1echar", '$["control\u0000\u001f\u001echar"]']; + + yield ['key"with\\"mixed', '$["key\\"with\\\\\\"mixed"]']; + yield ['\\"complex\\"case\\"', '$["\\\\\\"complex\\\\\\"case\\\\\\""]']; + yield ['json_like":{"value":"test"}', '$["json_like\\":{\\"value\\":\\"test\\"}"]']; + yield ['C:\\Program Files\\"App Name"', '$["C:\\\\Program Files\\\\\\"App Name\\""]']; + + yield ['key_with_Γ©_accents', '$["key_with_Γ©_accents"]']; + yield ['unicode_β†’_arrows', '$["unicode_β†’_arrows"]']; + yield ['chinese_δΈ­ζ–‡_key', '$["chinese_δΈ­ζ–‡_key"]']; + + yield ['', '$[""]']; + yield [' ', '$[" "]']; + yield [' spaces ', '$[" spaces "]']; + yield ["\t\n\r", '$["\\t\\n\\r"]']; + yield ["control\x00char", '$["control\u0000char"]']; + yield ["newline\nkey", '$["newline\\nkey"]']; + yield ["tab\tkey", '$["tab\\tkey"]']; + yield ["carriage\rreturn", '$["carriage\\rreturn"]']; } } 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