Skip to content

Commit f86b546

Browse files
committed
Breaking change: Return [] instead of false when no matches found
The only exception is when using smart get with a non-diverging path. In that case it will still return `false`
1 parent 468d4f0 commit f86b546

File tree

9 files changed

+72
-80
lines changed

9 files changed

+72
-80
lines changed

README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@ This is a [JSONPath](http://goessner.net/articles/JsonPath/) implementation for
1010

1111
This implementation features all elements in the specification except the `()` operator (in the spcecification there is the `$..a[(@.length-1)]`, but this can be achieved with `$..a[-1]` and the latter is simpler).
1212

13-
On top of this it implements some extended features:
14-
* Regex match key (p.e. `$.*[?(/^bo{2}k$/)]` or `$[?(/a\wthors/)]`).
15-
* Regex match value comparisons (p.e. `$.store.book[?(@.author =~ /.*Tolkien/)]`)
16-
* For the child operator `[]` there is no need to surround child names with quotes (p.e. `$.[store][book, bicycle]`) except if the name of the field is a non-valid javascript variable name.
17-
* `.length` can be used to get the length of a string, get the length of an array and to check if a node has children.
13+
In case of no matches for the query it will return an empty array.
1814

1915
Features
2016
========
2117
This implementation has the following features:
18+
* Regex match key (p.e. `$.*[?(/^bo{2}k$/)]` or `$[?(/a\wthors/)]`).
19+
* Regex match value comparisons (p.e. `$.store.book[?(@.author =~ /.*Tolkien/)]`)
20+
* For the child operator `[]` there is no need to surround child names with quotes (p.e. `$[store][book, bicycle]`) except if the name of the field is a non-valid javascript variable name.
21+
* `.length` can be used to get the length of a string, get the length of an array and to check if a node has children.
22+
* The `in` operator allows filtering for a value in a specified list: `$..[?(@.author in ["Nigel Rees", "Evelyn Waugh", $.store.book[3].author])]`
2223
* Object oriented implementation.
2324
* __Get__, __set__ and __add__ operations.
2425
* Magic methods implemented:
@@ -82,7 +83,8 @@ When creating a new instance of JsonObject, you can pass a second parameter to t
8283
This sets the behaviour of the instance to use SmartGet.
8384

8485
What SmartGet does is to determine if the given JsonPath branches at some point, if it does it behaves as usual;
85-
otherwise, it will directly return the value pointed by the given path (not the array containing it).
86+
otherwise, it will directly return the value pointed by the given path (not the
87+
array containing it) or `false` if not found.
8688

8789
GetJsonObjects
8890
--------------
@@ -134,7 +136,7 @@ dashes (`-`).
134136

135137
### Limitations on the specification:
136138
* The jsonpath inside _value_ cannot contain `or`, `and` or any comparator.
137-
* Jsonpaths in _value_ return the first element of the set or false if no result.
139+
* Jsonpaths in _value_ return the first element of the set or `false` if no result.
138140
* Boolean operations can't be grouped with parethesis.
139141
* `and`s are run before `or`s. That means that `a and 1 = b or c != d` is the same
140142
as `(a and 1) or (c != d)`

src/Galbar/JsonPath/Expression/Value.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ public static function evaluate(&$root, &$partial, $expression)
4242
if ($length) {
4343
$expression = $match[1];
4444
}
45-
$result = false;
45+
$result = [];
4646
if ($expression[0] === Language\Token::ROOT){
4747
list($result, $_) = \JsonPath\JsonPath::subtreeGet($root, $root, $expression);
4848
}
4949
else if ($expression[0] === Language\Token::CHILD) {
5050
$expression[0] = Language\Token::ROOT;
5151
list($result, $_) = \JsonPath\JsonPath::subtreeGet($root, $partial, $expression);
5252
}
53-
if ($result !== false) {
53+
if (!empty($result)) {
5454
if ($length) {
5555
if (is_array($result[0])) {
5656
return (float) count($result[0]);
@@ -65,7 +65,7 @@ public static function evaluate(&$root, &$partial, $expression)
6565
}
6666
return $result[0];
6767
}
68-
return $result;
68+
return false;
6969
}
7070
}
7171
}

src/Galbar/JsonPath/JsonObject.php

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,10 @@ public function getJson($options=0)
219219
public function get($jsonPath)
220220
{
221221
list($result, $hasDiverged) = JsonPath::get($this->jsonObject, $jsonPath);
222-
if ($this->smartGet && $result !== false && !$hasDiverged) {
222+
if ($this->smartGet && empty($result) && !$hasDiverged) {
223+
return false;
224+
}
225+
if ($this->smartGet && !empty($result) && !$hasDiverged) {
223226
return $result[0];
224227
}
225228
return $result;
@@ -244,19 +247,19 @@ public function get($jsonPath)
244247
public function getJsonObjects($jsonPath)
245248
{
246249
list($result, $hasDiverged) = JsonPath::get($this->jsonObject, $jsonPath);
247-
if ($result !== false) {
248-
$objs = array();
249-
foreach($result as &$value) {
250-
$jsonObject = new JsonObject(null, $this->smartGet);
251-
$jsonObject->jsonObject = &$value;
252-
$objs[] = $jsonObject;
253-
}
254-
if ($this->smartGet && !$hasDiverged) {
255-
return $objs[0];
256-
}
257-
return $objs;
250+
$objs = array();
251+
foreach($result as &$value) {
252+
$jsonObject = new JsonObject(null, $this->smartGet);
253+
$jsonObject->jsonObject = &$value;
254+
$objs[] = $jsonObject;
258255
}
259-
return $result;
256+
if ($this->smartGet && empty($result) && !$hasDiverged) {
257+
return false;
258+
}
259+
if ($this->smartGet && !empty($result) && !$hasDiverged) {
260+
return $objs[0];
261+
}
262+
return $objs;
260263
}
261264

262265
/**
@@ -278,10 +281,8 @@ public function getJsonObjects($jsonPath)
278281
public function set($jsonPath, $value)
279282
{
280283
list($result, $_) = JsonPath::get($this->jsonObject, $jsonPath, true);
281-
if ($result !== false) {
282-
foreach ($result as &$element) {
283-
$element = $value;
284-
}
284+
foreach ($result as &$element) {
285+
$element = $value;
285286
}
286287
return $this;
287288
}

src/Galbar/JsonPath/JsonPath.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ public static function subtreeGet(&$root, &$partial, $jsonPath, $createInexisten
5252
}
5353
}
5454
if (empty($newSelection)) {
55-
$selection = false;
55+
$selection = $newSelection;
56+
$hasDiverged = $hasDiverged || $newHasDiverged;
5657
break;
5758
} else {
5859
$jsonPath = $match[2];
@@ -64,7 +65,8 @@ public static function subtreeGet(&$root, &$partial, $jsonPath, $createInexisten
6465
$newSelection = array_merge($newSelection, $result);
6566
}
6667
if (empty($newSelection)) {
67-
$selection = false;
68+
$selection = $newSelection;
69+
$hasDiverged = $hasDiverged || $newHasDiverged;
6870
break;
6971
} else {
7072
$jsonPath = $match[1];
@@ -81,7 +83,8 @@ public static function subtreeGet(&$root, &$partial, $jsonPath, $createInexisten
8183
$newSelection = array_merge($newSelection, $result);
8284
}
8385
if (empty($newSelection)) {
84-
$selection = false;
86+
$selection = $newSelection;
87+
$hasDiverged = $hasDiverged || $newHasDiverged;
8588
break;
8689
} else {
8790
$jsonPath = "";

src/Galbar/JsonPath/Operation/GetChild.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,12 @@ class GetChild
2323
{
2424
public static function apply(&$jsonObject, $childName, $createInexistent = false)
2525
{
26+
$hasDiverged = $childName === Language\Token::ALL;
2627
if (!is_array($jsonObject)) {
27-
return array(array(), false);
28+
return array(array(), $hasDiverged);
2829
}
2930
$result = array();
30-
$hasDiverged = false;
3131
if ($childName === Language\Token::ALL) {
32-
$hasDiverged = true;
3332
foreach ($jsonObject as $key => $_) {
3433
$result[] = &$jsonObject[$key];
3534
}

src/Galbar/JsonPath/Operation/SelectChildren.php

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,6 @@ public static function apply(&$root, &$partial, $contents, $createInexistent = f
3737
foreach ($partial as $key => $item) {
3838
$result[] = &$partial[$key];
3939
}
40-
} else if (preg_match(Language\Regex::CHILD_NAME_LIST, $contents, $match)) {
41-
$names = array_map(
42-
function($x) { return trim($x, " \t\n\r\0\x0B'\""); },
43-
explode(Language\Token::COMA, $contents)
44-
);
45-
if (count($names) > 1) {
46-
$hasDiverged = true;
47-
}
48-
$result = Expression\ChildNameList::evaluate($partial, $names, $createInexistent);
4940
} else if (preg_match(Language\Regex::INDEX_LIST, $contents)) {
5041
$indexes = array_map(
5142
function($x) { return intval(trim($x)); },
@@ -55,6 +46,15 @@ function($x) { return intval(trim($x)); },
5546
$hasDiverged = true;
5647
}
5748
$result = Expression\IndexList::evaluate($partial, $indexes, $createInexistent);
49+
} else if (preg_match(Language\Regex::CHILD_NAME_LIST, $contents, $match)) {
50+
$names = array_map(
51+
function($x) { return trim($x, " \t\n\r\0\x0B'\""); },
52+
explode(Language\Token::COMA, $contents)
53+
);
54+
if (count($names) > 1) {
55+
$hasDiverged = true;
56+
}
57+
$result = Expression\ChildNameList::evaluate($partial, $names, $createInexistent);
5858
} else if (preg_match(Language\Regex::ARRAY_INTERVAL, $contents, $match)) {
5959
// end($match) has the matched group with the interval
6060
$numbers = explode(Language\Token::COLON, end($match));

tests/Galbar/JsonPath/JsonObjectIssue37Test.php

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,11 @@ public function testCase1()
3535
$this->assertEquals($expected, $result);
3636
}
3737

38-
/**
39-
* The test case in the issue suggests that in this case an empty array
40-
* should be returned. This seems to go against the original specification
41-
* of [JsonPath](https://goessner.net/articles/JsonPath/) where it states
42-
* that in case of no match `false` should be returned.
43-
*
44-
* This case is a "no match" kind of case so current behaviour is
45-
* considered correct.
46-
*/
4738
public function testCase2()
4839
{
4940
$jsonObject = new JsonObject('["first", "second"]');
5041
$result = $jsonObject->get('$[0:0]');
51-
$expected = false;
42+
$expected = [];
5243
$this->assertEquals($expected, $result);
5344
}
5445

@@ -93,20 +84,11 @@ public function testCase5()
9384
$this->assertEquals($expected, $result);
9485
}
9586

96-
/**
97-
* The test case in the issue suggests that in this case an empty array
98-
* should be returned. This seems to go against the original specification
99-
* of [JsonPath](https://goessner.net/articles/JsonPath/) where it states
100-
* that in case of no match `false` should be returned.
101-
*
102-
* This case is a "no match" kind of case so current behaviour is
103-
* considered correct.
104-
*/
10587
public function testCase6()
10688
{
10789
$jsonObject = new JsonObject('42');
10890
$result = $jsonObject->get('$..*');
109-
$expected = false;
91+
$expected = [];
11092
$this->assertEquals($expected, $result);
11193
}
11294
}

tests/Galbar/JsonPath/JsonObjectTest.php

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ public function testGetProvider()
157157
"$.store.bicycle"
158158
),
159159
array(
160-
false,
160+
[],
161161
"$.store.bicycl"
162162
),
163163
array(
@@ -170,7 +170,7 @@ public function testGetProvider()
170170
"$.store.book[*].price"
171171
),
172172
array(
173-
false,
173+
[],
174174
"$.store.book[7]"
175175
),
176176
array(
@@ -221,11 +221,11 @@ public function testGetProvider()
221221
"$.store.book[:2].price"
222222
),
223223
array(
224-
false,
224+
[],
225225
"$.store.bicycle.price[2]"
226226
),
227227
array(
228-
false,
228+
[],
229229
"$.store.bicycle.price.*"
230230
),
231231
array(
@@ -398,7 +398,7 @@ public function testGetProvider()
398398
"$.store[?(! @..price || @..color == 'red')].available"
399399
),
400400
array(
401-
false,
401+
[],
402402
"$.store[?(@.price.length == 3)]"
403403
),
404404
array(
@@ -408,7 +408,7 @@ public function testGetProvider()
408408
"$.store[?(@.color.length == 3)].price"
409409
),
410410
array(
411-
false,
411+
[],
412412
"$.store[?(@.color.length == 5)].price"
413413
),
414414
array(
@@ -496,6 +496,10 @@ public function testSmartGetProvider()
496496
false,
497497
"$.store.book[7]"
498498
),
499+
array(
500+
[],
501+
"$.store.book[7, 9]"
502+
),
499503
array(
500504
array(
501505
12.99,
@@ -541,7 +545,7 @@ public function testSmartGetProvider()
541545
"$.store.bicycle.price[2]"
542546
),
543547
array(
544-
false,
548+
[],
545549
"$.store.bicycle.price.*"
546550
),
547551
array(
@@ -702,7 +706,7 @@ public function testSmartGetProvider()
702706
"$.store[?(! @..price || @..color == 'red')].available"
703707
),
704708
array(
705-
false,
709+
[],
706710
"$.store[?(@.price.length == 3)]"
707711
),
708712
array(
@@ -712,7 +716,7 @@ public function testSmartGetProvider()
712716
"$.store[?(@.color.length == 3)].price"
713717
),
714718
array(
715-
false,
719+
[],
716720
"$.store[?(@.color.length == 5)].price"
717721
),
718722
array(
@@ -1191,17 +1195,18 @@ public function testGetJsonObjects()
11911195
$bike->set('$.price', 412);
11921196
$this->assertEquals($jsonObject->{'$.store.bicycle'}, $bike->getValue());
11931197

1194-
$this->assertFalse($jsonObject->getJsonObjects('$.abc'));
1198+
$this->assertEquals(false, $jsonObject->getJsonObjects('$.abc'));
1199+
$this->assertEquals([], $jsonObject->getJsonObjects('$[abc, 234f]'));
11951200
}
11961201

11971202
// Bug when using negative index triggers DivisionByZeroError
11981203
// https://github.com/Galbar/JsonPath-PHP/issues/60
11991204
public function testNegativeIndexOnEmptyArray() {
12001205
$object = new \JsonPath\JsonObject('{"data": []}');
1201-
$this->assertFalse($object->get('$.data[-1]'));
1206+
$this->assertEquals([], $object->get('$.data[-1]'));
12021207

12031208
$object = new \JsonPath\JsonObject('{"data": [{"id": 1},{"id": 2}]}');
1204-
$this->assertFalse($object->get('$.data[-5].id'));
1209+
$this->assertEquals([], $object->get('$.data[-5].id'));
12051210

12061211
$object = new \JsonPath\JsonObject('{"data": [{"id": 1}]}');
12071212
$this->assertEquals($object->get('$.data[-1].id'), [1]);
@@ -1210,9 +1215,9 @@ public function testNegativeIndexOnEmptyArray() {
12101215
$this->assertEquals($object->get('$.data[-1].id'), [2]);
12111216

12121217
$object = new \JsonPath\JsonObject('{"data": []}');
1213-
$this->assertFalse($object->get('$.data[1].id'));
1218+
$this->assertEquals([], $object->get('$.data[1].id'));
12141219

12151220
$object = new \JsonPath\JsonObject('{"data": [{"id": 1},{"id": 2}]}');
1216-
$this->assertFalse($object->get('$.data[3].id'));
1221+
$this->assertEquals([], $object->get('$.data[3].id'));
12171222
}
12181223
}

0 commit comments

Comments
 (0)