Skip to content

Commit 102a9b3

Browse files
committed
feat: implement feature parity with jsonpath-js 0.2.0
1 parent 9115387 commit 102a9b3

21 files changed

+289
-121
lines changed

.prettierignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
composer.lock
22
package-lock.json
3-
src/PeggyParser.php
3+
src/PeggyParser.php
4+
readme.md

composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,12 @@
3232
"src/parsers/root.php",
3333
"src/parsers/filter-selector.php",
3434
"src/types/nothing.php",
35+
"src/types/node.php",
3536
"src/PeggyParser.php",
3637
"src/parser.php",
3738
"src/JsonPath.php",
39+
"src/utils/enumerate-node.php",
40+
"src/utils/escape-member-name.php",
3841
"src/utils/is-equal.php",
3942
"src/utils/traverse-descendant.php",
4043
"src/utils/helpers.php",

readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Note that at least PHP 8.0 is needed to use jsonpath-php. For technical reasons,
2727
## Usage
2828

2929
```php
30-
use Loilo\JSONPath\JsonPath;
30+
use Loilo\JsonPath\JsonPath;
3131

3232
$query = new JsonPath('$.users[*].name');
3333

src/JsonPath.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ public function __construct(private $query)
1616
public function find($json)
1717
{
1818
$result_node_list = run($json, $this->root_node);
19-
return $result_node_list;
19+
return array_map(fn(Node $node) => $node->value, $result_node_list);
20+
}
21+
22+
public function paths($json)
23+
{
24+
$result_node_list = run($json, $this->root_node);
25+
return array_map(
26+
fn(Node $node) => [
27+
'value' => $node->value,
28+
'path' => $node->path,
29+
],
30+
$result_node_list,
31+
);
2032
}
2133
}

src/functions/function-definitions.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,15 @@ public function convert($arg)
2525
if (is_json_primitive($arg)) {
2626
return $arg;
2727
}
28-
if (is_json_array($arg)) {
28+
if (is_node($arg)) {
29+
return $arg->value;
30+
}
31+
if (is_node_list($arg)) {
2932
if (sizeof($arg) === 0) {
3033
return nothing();
3134
}
3235
if (sizeof($arg) === 1) {
33-
return $arg[0];
36+
return $arg[0]->value;
3437
}
3538
}
3639

@@ -51,7 +54,7 @@ public function get_type(): string
5154

5255
public function convert($arg)
5356
{
54-
if (is_json_array($arg)) {
57+
if (is_node_list($arg)) {
5558
return $arg;
5659
}
5760

src/functions/length.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ function ($node) {
3636

3737
// If the argument value is an object, the result is the number of members in the object.
3838
if (is_json_object($node)) {
39-
return sizeof(get_object_vars($node));
39+
return sizeof(is_array($node) ? $node : get_object_vars($node));
4040
}
4141

4242
// For any other argument value, the result is the special result Nothing.

src/functions/value.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ function ($nodes) {
2323
// Its only argument is an instance of NodesType (possibly taken from a filter-query, as in the example above). The result is an instance of ValueType.
2424
// If the argument contains a single node, the result is the value of the node.
2525
if (sizeof($nodes) === 1) {
26-
return $nodes[0];
26+
return $nodes[0]->value;
2727
}
2828

2929
// If the argument is the empty nodelist or contains multiple nodes, the result is Nothing.

src/parser.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44

55
function run($json, $query): array
66
{
7-
$root_node = $json;
7+
$root_node = create_node($json, '$');
88
return apply_root($query, $root_node);
99
}

src/parsers/array-slice-selector.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@
77
// It matches elements from arrays starting at index < start >
88
// and ending at(but not including)<end>,
99
// while incrementing by step with a default of 1.
10-
function apply_slice_selector($selector, $json)
10+
function apply_slice_selector($selector, Node $node)
1111
{
12-
if (!is_json_array($json)) {
12+
if (!is_json_array($node->value)) {
1313
return [];
1414
}
1515

1616
$step = $selector->step ?? 1;
17-
$start = $selector->start ?? ($step >= 0 ? 0 : sizeof($json) - 1);
18-
$end = $selector->end ?? ($step >= 0 ? sizeof($json) : -sizeof($json) - 1);
17+
$start = $selector->start ?? ($step >= 0 ? 0 : sizeof($node->value) - 1);
18+
$end = $selector->end ?? ($step >= 0 ? sizeof($node->value) : -sizeof($node->value) - 1);
1919
$array = [];
2020

21-
[$lower, $upper] = bounds($start, $end, $step, sizeof($json));
21+
[$lower, $upper] = bounds($start, $end, $step, sizeof($node->value));
2222

2323
// IF step > 0 THEN
2424
//
@@ -39,11 +39,11 @@ function apply_slice_selector($selector, $json)
3939
// END IF
4040
if ($step > 0) {
4141
for ($i = $lower; $i < $upper; $i += $step) {
42-
$array[] = $json[$i];
42+
$array[] = add_index_path($node, $node->value[$i], $i);
4343
}
4444
} elseif ($step < 0) {
4545
for ($i = $upper; $lower < $i; $i += $step) {
46-
$array[] = $json[$i];
46+
$array[] = add_index_path($node, $node->value[$i], $i);
4747
}
4848
}
4949

src/parsers/filter-selector.php

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,37 @@
22

33
namespace Loilo\JsonPath;
44

5-
function apply_filter_selector($selector, $root_node, $json)
5+
function apply_filter_selector($selector, $root_node, Node $node)
66
{
77
// The filter selector works with arrays and objects exclusively.
88
// Its result is a list of(zero, one, multiple, or all)
99
// their array elements or member values, respectively.
1010
// Applied to a primitive value,
1111
// it selects nothing(and therefore does not contribute to the result of the filter selector).
12-
if (is_json_primitive($json)) {
12+
if (is_json_primitive($node->value)) {
1313
return [];
1414
}
1515

16-
return Array\filter(to_array($json), function ($item) use ($selector, $root_node) {
17-
return apply_filter_expression($selector->expr, $root_node, $item);
16+
return Array\filter(enumerate_node($node), function (Node $node) use ($selector, $root_node) {
17+
return apply_filter_expression($selector->expr, $root_node, $node);
1818
});
1919
}
2020

21-
function apply_filter_expression($expr, $root_node, $json)
21+
function apply_filter_expression($expr, $root_node, Node $node)
2222
{
2323
$exp_type = $expr->type;
2424
return match ($exp_type) {
25-
'ComparisonExpr' => apply_compare($expr, $root_node, $json),
26-
'TestExpr' => apply_test($expr, $root_node, $json),
27-
'LogicalBinary', 'LogicalUnary' => apply_logical($expr, $root_node, $json),
25+
'ComparisonExpr' => apply_compare($expr, $root_node, $node),
26+
'TestExpr' => apply_test($expr, $root_node, $node),
27+
'LogicalBinary', 'LogicalUnary' => apply_logical($expr, $root_node, $node),
2828
default => throw new \Exception("Unexpected expression type: $exp_type"),
2929
};
3030
}
3131

32-
function apply_compare($compare, $root_node, $json)
32+
function apply_compare($compare, $root_node, Node $node)
3333
{
34-
$left = apply_comparable($compare->left, $root_node, $json);
35-
$right = apply_comparable($compare->right, $root_node, $json);
34+
$left = apply_comparable($compare->left, $root_node, $node);
35+
$right = apply_comparable($compare->right, $root_node, $node);
3636

3737
return eval_compare($left, $right, $compare->operator);
3838
}
@@ -86,33 +86,33 @@ function apply_current_node($current_node, $root_node, $node_list)
8686
return apply_segments($current_node->segments, $root_node, $node_list);
8787
}
8888

89-
function apply_comparable($comparable, $root_node, $json)
89+
function apply_comparable($comparable, $root_node, Node $node)
9090
{
9191
// These can be obtained via literal values; singular queries,
9292
// each of which selects at most one node
9393
return match ($comparable->type) {
9494
'Literal' => $comparable->member,
9595
'CurrentNode' => array_key_exists(
9696
0,
97-
$result = apply_current_node($comparable, $root_node, [$json]),
97+
$result = apply_current_node($comparable, $root_node, [$node]),
9898
)
99-
? $result[0]
99+
? $result[0]->value
100100
: nothing(),
101-
'Root' => apply_root($comparable, $root_node)[0] ?? nothing(),
102-
'FunctionExpr' => apply_function($comparable, $root_node, $json),
101+
'Root' => apply_root($comparable, $root_node)[0]?->value ?? nothing(),
102+
'FunctionExpr' => apply_function($comparable, $root_node, $node),
103103
};
104104
}
105105

106-
function apply_test($expr, $root_node, $json)
106+
function apply_test($expr, $root_node, Node $node)
107107
{
108-
return apply_query($expr->query, $root_node, $json);
108+
return apply_query($expr->query, $root_node, $node);
109109
}
110110

111-
function apply_query($query, $root_node, $json)
111+
function apply_query($query, $root_node, Node $node)
112112
{
113113
switch ($query->type) {
114114
case 'FunctionExpr':
115-
$function_result = apply_function($query, $root_node, $json);
115+
$function_result = apply_function($query, $root_node, $node);
116116

117117
// LogicalType
118118
if ($function_result === LogicalType::true()) {
@@ -129,40 +129,40 @@ function apply_query($query, $root_node, $json)
129129
// ValueType
130130
throw new \Exception("Function {$query->name} result must be compared");
131131
case 'CurrentNode':
132-
return sizeof(apply_current_node($query, $root_node, [$json])) > 0;
132+
return sizeof(apply_current_node($query, $root_node, [$node])) > 0;
133133
case 'Root':
134134
return sizeof(apply_root($query, $root_node)) > 0;
135135
}
136136

137137
return false;
138138
}
139139

140-
function apply_logical($expr, $root_node, $json)
140+
function apply_logical($expr, $root_node, Node $node)
141141
{
142142
return match ($expr->operator) {
143-
'||' => apply_or($expr, $root_node, $json),
144-
'&&' => apply_and($expr, $root_node, $json),
145-
'!' => apply_not($expr, $root_node, $json),
143+
'||' => apply_or($expr, $root_node, $node),
144+
'&&' => apply_and($expr, $root_node, $node),
145+
'!' => apply_not($expr, $root_node, $node),
146146
};
147147
}
148148

149-
function apply_or($or, $root_node, $json)
149+
function apply_or($or, $root_node, Node $node)
150150
{
151151
// TODO: make efficient
152-
$left = apply_filter_expression($or->left, $root_node, $json);
153-
$right = apply_filter_expression($or->right, $root_node, $json);
152+
$left = apply_filter_expression($or->left, $root_node, $node);
153+
$right = apply_filter_expression($or->right, $root_node, $node);
154154
return $left || $right;
155155
}
156156

157-
function apply_and($and, $root_node, $json)
157+
function apply_and($and, $root_node, Node $node)
158158
{
159-
$left = apply_filter_expression($and->left, $root_node, $json);
160-
$right = apply_filter_expression($and->right, $root_node, $json);
159+
$left = apply_filter_expression($and->left, $root_node, $node);
160+
$right = apply_filter_expression($and->right, $root_node, $node);
161161
return $left && $right;
162162
}
163163

164-
function apply_not($not, $root_node, $json)
164+
function apply_not($not, $root_node, Node $node)
165165
{
166-
$result = apply_filter_expression($not->expr, $root_node, $json);
166+
$result = apply_filter_expression($not->expr, $root_node, $node);
167167
return !$result;
168168
}

0 commit comments

Comments
 (0)