Skip to content

Commit b728a84

Browse files
committed
Added support for negated IN expressions
I don't know, if this is a thing or not, but since we already have the IN expression, I don't see a reason not to provide a negated version (!IN). + Updated tests (increased coverage, yay!) + Updated README
1 parent ecdc023 commit b728a84

File tree

5 files changed

+65
-23
lines changed

5 files changed

+65
-23
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,13 @@ Script expressions are not supported as the original author intended because:
107107

108108
So here are the types of query expressions that are supported:
109109

110-
[?(@._KEY_ _OPERATOR_ _VALUE_)] // <, >, !=, == and in
110+
[?(@._KEY_ _OPERATOR_ _VALUE_)] // <, >, !=, ==, in and !in
111111
Eg.
112112
[?(@.title == "A string")] //
113113
[?(@.title = "A string")]
114114
// A single equals is not an assignment but the SQL-style of '=='
115115
[?(@.title in ["A string", "Another string"])]
116+
[?(@.title !in ["A string", "Another string"])]
116117

117118
Known issues
118119
------

src/Filters/QueryMatchFilter.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class QueryMatchFilter extends AbstractFilter
2020
{
2121
public const MATCH_QUERY_OPERATORS = '
2222
@(\.(?<key>[^ =]+)|\[["\']?(?<keySquare>.*?)["\']?\])
23-
(\s*(?<operator>==|=|<>|!==|!=|>|<|in)\s*(?<comparisonValue>.+))?
23+
(\s*(?<operator>==|=|<>|!==|!=|>|<|in|!in)\s*(?<comparisonValue>.+))?
2424
';
2525

2626
/**
@@ -100,6 +100,10 @@ public function filter($collection): array
100100
if ($operator === 'in' && is_array($comparisonValue) && in_array($value1, $comparisonValue, true)) {
101101
$return[] = $value;
102102
}
103+
104+
if ($operator === '!in' && is_array($comparisonValue) && !in_array($value1, $comparisonValue, true)) {
105+
$return[] = $value;
106+
}
103107
}
104108
}
105109

src/JSONPath.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public function find($expression): self
5757
$collectionData = [$this->data];
5858

5959
foreach ($tokens as $token) {
60+
/** @var JSONPathToken $token */
6061
$filter = $token->buildFilter($this->options);
6162

6263
$filteredData = [];

tests/JSONPathLexerTest.php

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,27 @@ class JSONPathLexerTest extends TestCase
1919
/**
2020
* @throws JSONPathException
2121
*/
22-
public function test_Index_Wildcard(): void
22+
public function testEmptyExpression(): void
23+
{
24+
$tokens = (new JSONPathLexer(''))->parseExpression();
25+
26+
self::assertEmpty($tokens);
27+
}
28+
29+
/**
30+
* @throws JSONPathException
31+
*/
32+
public function testDollarOnlyExpression(): void
33+
{
34+
$tokens = (new JSONPathLexer('$'))->parseExpression();
35+
36+
self::assertEmpty($tokens);
37+
}
38+
39+
/**
40+
* @throws JSONPathException
41+
*/
42+
public function testIndexWildcard(): void
2343
{
2444
$tokens = (new JSONPathLexer('.*'))->parseExpression();
2545

@@ -30,7 +50,7 @@ public function test_Index_Wildcard(): void
3050
/**
3151
* @throws JSONPathException
3252
*/
33-
public function test_Index_Simple(): void
53+
public function testIndexSimple(): void
3454
{
3555
$tokens = (new JSONPathLexer('.foo'))->parseExpression();
3656

@@ -41,7 +61,7 @@ public function test_Index_Simple(): void
4161
/**
4262
* @throws JSONPathException
4363
*/
44-
public function test_Index_Recursive(): void
64+
public function testIndexRecursive(): void
4565
{
4666
$tokens = (new JSONPathLexer('..teams.*'))->parseExpression();
4767

@@ -57,7 +77,7 @@ public function test_Index_Recursive(): void
5777
/**
5878
* @throws JSONPathException
5979
*/
60-
public function test_Index_Complex(): void
80+
public function testIndexComplex(): void
6181
{
6282
$tokens = (new JSONPathLexer('["\'b.^*_"]'))->parseExpression();
6383

@@ -68,7 +88,7 @@ public function test_Index_Complex(): void
6888
/**
6989
* @throws JSONPathException
7090
*/
71-
public function test_Index_BadlyFormed(): void
91+
public function testIndexBadlyFormed(): void
7292
{
7393
$this->expectException(JSONPathException::class);
7494
$this->expectExceptionMessage('Unable to parse token hello* in expression: .hello*');
@@ -79,7 +99,7 @@ public function test_Index_BadlyFormed(): void
7999
/**
80100
* @throws JSONPathException
81101
*/
82-
public function test_Index_Integer(): void
102+
public function testIndexInteger(): void
83103
{
84104
$tokens = (new JSONPathLexer('[0]'))->parseExpression();
85105

@@ -90,7 +110,7 @@ public function test_Index_Integer(): void
90110
/**
91111
* @throws JSONPathException
92112
*/
93-
public function test_Index_IntegerAfterDotNotation(): void
113+
public function testIndexIntegerAfterDotNotation(): void
94114
{
95115
$tokens = (new JSONPathLexer('.books[0]'))->parseExpression();
96116

@@ -103,7 +123,7 @@ public function test_Index_IntegerAfterDotNotation(): void
103123
/**
104124
* @throws JSONPathException
105125
*/
106-
public function test_Index_Word(): void
126+
public function testIndexWord(): void
107127
{
108128
$tokens = (new JSONPathLexer('["foo$-/\'"]'))->parseExpression();
109129

@@ -114,7 +134,7 @@ public function test_Index_Word(): void
114134
/**
115135
* @throws JSONPathException
116136
*/
117-
public function test_Index_WordWithWhitespace(): void
137+
public function testIndexWordWithWhitespace(): void
118138
{
119139
$tokens = (new JSONPathLexer('[ "foo$-/\'" ]'))->parseExpression();
120140

@@ -125,7 +145,7 @@ public function test_Index_WordWithWhitespace(): void
125145
/**
126146
* @throws JSONPathException
127147
*/
128-
public function test_Slice_Simple(): void
148+
public function testSliceSimple(): void
129149
{
130150
$tokens = (new JSONPathLexer('[0:1:2]'))->parseExpression();
131151

@@ -136,7 +156,7 @@ public function test_Slice_Simple(): void
136156
/**
137157
* @throws JSONPathException
138158
*/
139-
public function test_Index_NegativeIndex(): void
159+
public function testIndexNegativeIndex(): void
140160
{
141161
$tokens = (new JSONPathLexer('[-1]'))->parseExpression();
142162

@@ -147,7 +167,7 @@ public function test_Index_NegativeIndex(): void
147167
/**
148168
* @throws JSONPathException
149169
*/
150-
public function test_Slice_AllNull(): void
170+
public function testSliceAllNull(): void
151171
{
152172
$tokens = (new JSONPathLexer('[:]'))->parseExpression();
153173

@@ -158,7 +178,7 @@ public function test_Slice_AllNull(): void
158178
/**
159179
* @throws JSONPathException
160180
*/
161-
public function test_QueryResult_Simple(): void
181+
public function testQueryResultSimple(): void
162182
{
163183
$tokens = (new JSONPathLexer('[(@.foo + 2)]'))->parseExpression();
164184

@@ -169,7 +189,7 @@ public function test_QueryResult_Simple(): void
169189
/**
170190
* @throws JSONPathException
171191
*/
172-
public function test_QueryMatch_Simple(): void
192+
public function testQueryMatchSimple(): void
173193
{
174194
$tokens = (new JSONPathLexer('[?(@.foo < \'bar\')]'))->parseExpression();
175195

@@ -180,7 +200,7 @@ public function test_QueryMatch_Simple(): void
180200
/**
181201
* @throws JSONPathException
182202
*/
183-
public function test_QueryMatch_NotEqualTO(): void
203+
public function testQueryMatchNotEqualTO(): void
184204
{
185205
$tokens = (new JSONPathLexer('[?(@.foo != \'bar\')]'))->parseExpression();
186206

@@ -191,7 +211,7 @@ public function test_QueryMatch_NotEqualTO(): void
191211
/**
192212
* @throws JSONPathException
193213
*/
194-
public function test_QueryMatch_Brackets(): void
214+
public function testQueryMatchBrackets(): void
195215
{
196216
$tokens = (new JSONPathLexer("[?(@['@language']='en')]"))->parseExpression();
197217

@@ -203,7 +223,7 @@ public function test_QueryMatch_Brackets(): void
203223
/**
204224
* @throws JSONPathException
205225
*/
206-
public function test_Recursive_Simple(): void
226+
public function testRecursiveSimple(): void
207227
{
208228
$tokens = (new JSONPathLexer('..foo'))->parseExpression();
209229

@@ -216,7 +236,7 @@ public function test_Recursive_Simple(): void
216236
/**
217237
* @throws JSONPathException
218238
*/
219-
public function test_Recursive_Wildcard(): void
239+
public function testRecursiveWildcard(): void
220240
{
221241
$tokens = (new JSONPathLexer('..*'))->parseExpression();
222242

@@ -229,7 +249,7 @@ public function test_Recursive_Wildcard(): void
229249
/**
230250
* @throws JSONPathException
231251
*/
232-
public function test_Recursive_BadlyFormed(): void
252+
public function testRecursiveBadlyFormed(): void
233253
{
234254
$this->expectException(JSONPathException::class);
235255
$this->expectExceptionMessage('Unable to parse token ba^r in expression: ..ba^r');
@@ -240,7 +260,7 @@ public function test_Recursive_BadlyFormed(): void
240260
/**
241261
* @throws JSONPathException
242262
*/
243-
public function test_Indexes_Simple(): void
263+
public function testIndexesSimple(): void
244264
{
245265
$tokens = (new JSONPathLexer('[1,2,3]'))->parseExpression();
246266

@@ -251,7 +271,7 @@ public function test_Indexes_Simple(): void
251271
/**
252272
* @throws JSONPathException
253273
*/
254-
public function test_Indexes_Whitespace(): void
274+
public function testIndexesWhitespace(): void
255275
{
256276
$tokens = (new JSONPathLexer('[ 1,2 , 3]'))->parseExpression();
257277

tests/JSONPathTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ public function testChildOperators(): void
3131
self::assertEquals('Sayings of the Century', $result[0]);
3232
}
3333

34+
/**
35+
* @throws Exception
36+
*/
3437
public function testIndexesObject(): void
3538
{
3639
$result = (new JSONPath($this->exampleIndexedObject(random_int(0, 1))))->find('$.store.books[3].title');
@@ -273,6 +276,19 @@ public function testQueryMatchIn(): void
273276
self::assertEquals(['Sayings of the Century', 'The Lord of the Rings'], $result->getData());
274277
}
275278

279+
/**
280+
* $..books[?(@.author not in ["J. R. R. Tolkien", "Nigel Rees"])]
281+
* Filter books that don't have a title in ["...", "..."]
282+
*
283+
* @throws Exception
284+
*/
285+
public function testQueryMatchNotIn(): void
286+
{
287+
$result = (new JSONPath($this->exampleData(random_int(0, 1))))->find('$..books[?(@.author !in ["J. R. R. Tolkien", "Nigel Rees"])].title');
288+
289+
self::assertEquals(['Sword of Honour', 'Moby Dick'], $result->getData());
290+
}
291+
276292
/**
277293
* $.store.books[*].author
278294
*

0 commit comments

Comments
 (0)