@loancrate/json-selector 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -3
- package/dist/__generated__/parser.d.ts.map +1 -1
- package/dist/access.d.ts +6 -6
- package/dist/access.d.ts.map +1 -1
- package/dist/ast.d.ts +6 -3
- package/dist/ast.d.ts.map +1 -1
- package/dist/evaluate.d.ts +5 -5
- package/dist/evaluate.d.ts.map +1 -1
- package/dist/format.d.ts.map +1 -1
- package/dist/json-selector.esm.js +3510 -0
- package/dist/json-selector.esm.js.map +1 -0
- package/dist/json-selector.umd.js +3575 -0
- package/dist/json-selector.umd.js.map +1 -0
- package/dist/visitor.d.ts +2 -1
- package/dist/visitor.d.ts.map +1 -1
- package/package.json +32 -20
- package/src/__generated__/parser.js +2730 -0
- package/src/access.ts +534 -0
- package/src/ast.ts +114 -0
- package/src/evaluate.ts +269 -0
- package/src/format.ts +144 -0
- package/src/get.ts +9 -0
- package/src/grammar.pegjs +226 -0
- package/src/index.ts +7 -0
- package/src/parse.ts +7 -0
- package/src/set.ts +13 -0
- package/src/util.ts +70 -0
- package/src/visitor.ts +79 -0
- package/dist/__generated__/parser.js +0 -2855
- package/dist/__generated__/parser.js.map +0 -1
- package/dist/access.js +0 -444
- package/dist/access.js.map +0 -1
- package/dist/ast.js +0 -3
- package/dist/ast.js.map +0 -1
- package/dist/evaluate.js +0 -168
- package/dist/evaluate.js.map +0 -1
- package/dist/format.js +0 -119
- package/dist/format.js.map +0 -1
- package/dist/get.js +0 -9
- package/dist/get.js.map +0 -1
- package/dist/index.js +0 -26
- package/dist/index.js.map +0 -1
- package/dist/parse.js +0 -10
- package/dist/parse.js.map +0 -1
- package/dist/set.js +0 -12
- package/dist/set.js.map +0 -1
- package/dist/util.js +0 -70
- package/dist/util.js.map +0 -1
- package/dist/visitor.js +0 -39
- package/dist/visitor.js.map +0 -1
package/src/evaluate.ts
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import deepEqual from "fast-deep-equal";
|
|
2
|
+
import { JsonSelector, JsonSelectorCompareOperator } from "./ast";
|
|
3
|
+
import { findId, getField, getIndex, isArray, isFalseOrEmpty } from "./util";
|
|
4
|
+
import { visitJsonSelector } from "./visitor";
|
|
5
|
+
|
|
6
|
+
export function evaluateJsonSelector(
|
|
7
|
+
selector: JsonSelector,
|
|
8
|
+
context: unknown,
|
|
9
|
+
rootContext = context
|
|
10
|
+
): unknown {
|
|
11
|
+
return visitJsonSelector<unknown, unknown>(
|
|
12
|
+
selector,
|
|
13
|
+
{
|
|
14
|
+
current() {
|
|
15
|
+
return context;
|
|
16
|
+
},
|
|
17
|
+
root() {
|
|
18
|
+
return rootContext;
|
|
19
|
+
},
|
|
20
|
+
literal({ value }) {
|
|
21
|
+
return value;
|
|
22
|
+
},
|
|
23
|
+
identifier({ id }) {
|
|
24
|
+
return getField(context, id);
|
|
25
|
+
},
|
|
26
|
+
fieldAccess({ expression, field }) {
|
|
27
|
+
return getField(
|
|
28
|
+
evaluateJsonSelector(expression, context, rootContext),
|
|
29
|
+
field
|
|
30
|
+
);
|
|
31
|
+
},
|
|
32
|
+
indexAccess({ expression, index }) {
|
|
33
|
+
return getIndex(
|
|
34
|
+
evaluateJsonSelector(expression, context, rootContext),
|
|
35
|
+
index
|
|
36
|
+
);
|
|
37
|
+
},
|
|
38
|
+
idAccess({ expression, id }) {
|
|
39
|
+
return findId(
|
|
40
|
+
evaluateJsonSelector(expression, context, rootContext),
|
|
41
|
+
id
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
project({ expression, projection }) {
|
|
45
|
+
return project(
|
|
46
|
+
evaluateJsonSelector(expression, context, rootContext),
|
|
47
|
+
projection,
|
|
48
|
+
rootContext
|
|
49
|
+
);
|
|
50
|
+
},
|
|
51
|
+
filter({ expression, condition }) {
|
|
52
|
+
return filter(
|
|
53
|
+
evaluateJsonSelector(expression, context, rootContext),
|
|
54
|
+
condition,
|
|
55
|
+
rootContext
|
|
56
|
+
);
|
|
57
|
+
},
|
|
58
|
+
slice({ expression, start, end, step }) {
|
|
59
|
+
return slice(
|
|
60
|
+
evaluateJsonSelector(expression, context, rootContext),
|
|
61
|
+
start,
|
|
62
|
+
end,
|
|
63
|
+
step
|
|
64
|
+
);
|
|
65
|
+
},
|
|
66
|
+
flatten({ expression }) {
|
|
67
|
+
return flatten(evaluateJsonSelector(expression, context, rootContext));
|
|
68
|
+
},
|
|
69
|
+
not({ expression }) {
|
|
70
|
+
return isFalseOrEmpty(
|
|
71
|
+
evaluateJsonSelector(expression, context, rootContext)
|
|
72
|
+
);
|
|
73
|
+
},
|
|
74
|
+
compare({ lhs, rhs, operator }) {
|
|
75
|
+
const lv = evaluateJsonSelector(lhs, context, rootContext);
|
|
76
|
+
const rv = evaluateJsonSelector(rhs, context, rootContext);
|
|
77
|
+
return compare(lv, rv, operator);
|
|
78
|
+
},
|
|
79
|
+
and({ lhs, rhs }) {
|
|
80
|
+
const lv = evaluateJsonSelector(lhs, context, rootContext);
|
|
81
|
+
return isFalseOrEmpty(lv)
|
|
82
|
+
? lv
|
|
83
|
+
: evaluateJsonSelector(rhs, context, rootContext);
|
|
84
|
+
},
|
|
85
|
+
or({ lhs, rhs }) {
|
|
86
|
+
const lv = evaluateJsonSelector(lhs, context, rootContext);
|
|
87
|
+
return !isFalseOrEmpty(lv)
|
|
88
|
+
? lv
|
|
89
|
+
: evaluateJsonSelector(rhs, context, rootContext);
|
|
90
|
+
},
|
|
91
|
+
pipe({ lhs, rhs }) {
|
|
92
|
+
return evaluateJsonSelector(
|
|
93
|
+
rhs,
|
|
94
|
+
evaluateJsonSelector(lhs, context, rootContext),
|
|
95
|
+
rootContext
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
context
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function project(
|
|
104
|
+
value: unknown[],
|
|
105
|
+
projection: JsonSelector | undefined,
|
|
106
|
+
rootContext: unknown
|
|
107
|
+
): unknown[];
|
|
108
|
+
export function project(
|
|
109
|
+
value: unknown,
|
|
110
|
+
projection: JsonSelector | undefined,
|
|
111
|
+
rootContext: unknown
|
|
112
|
+
): unknown[] | null;
|
|
113
|
+
export function project(
|
|
114
|
+
value: unknown,
|
|
115
|
+
projection: JsonSelector | undefined,
|
|
116
|
+
rootContext: unknown
|
|
117
|
+
): unknown[] | null {
|
|
118
|
+
if (!isArray(value)) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
if (!projection) {
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
const result = value
|
|
125
|
+
.map((e) => evaluateJsonSelector(projection, e, rootContext))
|
|
126
|
+
.filter((e) => e != null);
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function filter(
|
|
131
|
+
value: unknown[],
|
|
132
|
+
condition: JsonSelector,
|
|
133
|
+
rootContext: unknown
|
|
134
|
+
): unknown[];
|
|
135
|
+
export function filter(
|
|
136
|
+
value: unknown,
|
|
137
|
+
condition: JsonSelector,
|
|
138
|
+
rootContext: unknown
|
|
139
|
+
): unknown[] | null;
|
|
140
|
+
export function filter(
|
|
141
|
+
value: unknown,
|
|
142
|
+
condition: JsonSelector,
|
|
143
|
+
rootContext: unknown
|
|
144
|
+
): unknown[] | null {
|
|
145
|
+
if (!isArray(value)) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const result = value.filter(
|
|
149
|
+
(e) => !isFalseOrEmpty(evaluateJsonSelector(condition, e, rootContext))
|
|
150
|
+
);
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function slice(
|
|
155
|
+
value: unknown[],
|
|
156
|
+
start: number | undefined,
|
|
157
|
+
end?: number,
|
|
158
|
+
step?: number
|
|
159
|
+
): unknown[];
|
|
160
|
+
export function slice(
|
|
161
|
+
value: unknown,
|
|
162
|
+
start: number | undefined,
|
|
163
|
+
end?: number,
|
|
164
|
+
step?: number
|
|
165
|
+
): unknown[] | null;
|
|
166
|
+
export function slice(
|
|
167
|
+
value: unknown,
|
|
168
|
+
start: number | undefined,
|
|
169
|
+
end?: number,
|
|
170
|
+
step?: number
|
|
171
|
+
): unknown[] | null {
|
|
172
|
+
if (!isArray(value)) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
({ start, end, step } = normalizeSlice(value.length, start, end, step));
|
|
176
|
+
const collected: unknown[] = [];
|
|
177
|
+
if (step > 0) {
|
|
178
|
+
for (let i = start; i < end; i += step) {
|
|
179
|
+
collected.push(value[i]);
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
for (let i = start; i > end; i += step) {
|
|
183
|
+
collected.push(value[i]);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return collected;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function normalizeSlice(
|
|
190
|
+
length: number,
|
|
191
|
+
start?: number,
|
|
192
|
+
end?: number,
|
|
193
|
+
step?: number
|
|
194
|
+
): { start: number; end: number; step: number } {
|
|
195
|
+
if (step == null) {
|
|
196
|
+
step = 1;
|
|
197
|
+
} else if (step === 0) {
|
|
198
|
+
throw new Error("Invalid slice: step cannot be 0");
|
|
199
|
+
}
|
|
200
|
+
if (start == null) {
|
|
201
|
+
start = step < 0 ? length - 1 : 0;
|
|
202
|
+
} else {
|
|
203
|
+
start = limitSlice(start, step, length);
|
|
204
|
+
}
|
|
205
|
+
if (end == null) {
|
|
206
|
+
end = step < 0 ? -1 : length;
|
|
207
|
+
} else {
|
|
208
|
+
end = limitSlice(end, step, length);
|
|
209
|
+
}
|
|
210
|
+
return { start, end, step };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function limitSlice(value: number, step: number, length: number): number {
|
|
214
|
+
if (value < 0) {
|
|
215
|
+
value += length;
|
|
216
|
+
if (value < 0) {
|
|
217
|
+
value = step < 0 ? -1 : 0;
|
|
218
|
+
}
|
|
219
|
+
} else if (value >= length) {
|
|
220
|
+
value = step < 0 ? length - 1 : length;
|
|
221
|
+
}
|
|
222
|
+
return value;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function flatten(value: unknown[]): unknown[];
|
|
226
|
+
export function flatten(value: unknown): unknown[] | null;
|
|
227
|
+
export function flatten(value: unknown): unknown[] | null {
|
|
228
|
+
return isArray(value) ? value.flat() : null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function compare(
|
|
232
|
+
lv: number,
|
|
233
|
+
rv: number,
|
|
234
|
+
operator: JsonSelectorCompareOperator
|
|
235
|
+
): boolean;
|
|
236
|
+
export function compare(
|
|
237
|
+
lv: unknown,
|
|
238
|
+
rv: unknown,
|
|
239
|
+
operator: JsonSelectorCompareOperator
|
|
240
|
+
): boolean | null;
|
|
241
|
+
export function compare(
|
|
242
|
+
lv: unknown,
|
|
243
|
+
rv: unknown,
|
|
244
|
+
operator: JsonSelectorCompareOperator
|
|
245
|
+
): boolean | null {
|
|
246
|
+
switch (operator) {
|
|
247
|
+
case "==":
|
|
248
|
+
return deepEqual(lv, rv);
|
|
249
|
+
case "!=":
|
|
250
|
+
return !deepEqual(lv, rv);
|
|
251
|
+
case "<":
|
|
252
|
+
case "<=":
|
|
253
|
+
case ">":
|
|
254
|
+
case ">=":
|
|
255
|
+
if (typeof lv === "number" && typeof rv === "number") {
|
|
256
|
+
switch (operator) {
|
|
257
|
+
case "<":
|
|
258
|
+
return lv < rv;
|
|
259
|
+
case "<=":
|
|
260
|
+
return lv <= rv;
|
|
261
|
+
case ">":
|
|
262
|
+
return lv > rv;
|
|
263
|
+
case ">=":
|
|
264
|
+
return lv >= rv;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return null;
|
|
269
|
+
}
|
package/src/format.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { JsonSelector, JsonSelectorNodeType } from "./ast";
|
|
2
|
+
import { formatIdentifier, formatLiteral, formatRawString } from "./util";
|
|
3
|
+
import { visitJsonSelector } from "./visitor";
|
|
4
|
+
|
|
5
|
+
const PRECEDENCE_ACCESS = 1;
|
|
6
|
+
const PRECEDENCE_NOT = 2;
|
|
7
|
+
const PRECEDENCE_COMPARE = 3;
|
|
8
|
+
const PRECEDENCE_AND = 4;
|
|
9
|
+
const PRECEDENCE_OR = 5;
|
|
10
|
+
const PRECEDENCE_PIPE = 6;
|
|
11
|
+
const PRECEDENCE_MAX = 7;
|
|
12
|
+
|
|
13
|
+
const operatorPrecedence: { [type in JsonSelectorNodeType]?: number } = {
|
|
14
|
+
fieldAccess: PRECEDENCE_ACCESS,
|
|
15
|
+
idAccess: PRECEDENCE_ACCESS,
|
|
16
|
+
indexAccess: PRECEDENCE_ACCESS,
|
|
17
|
+
project: PRECEDENCE_ACCESS,
|
|
18
|
+
filter: PRECEDENCE_ACCESS,
|
|
19
|
+
slice: PRECEDENCE_ACCESS,
|
|
20
|
+
flatten: PRECEDENCE_ACCESS,
|
|
21
|
+
not: PRECEDENCE_NOT,
|
|
22
|
+
compare: PRECEDENCE_COMPARE,
|
|
23
|
+
and: PRECEDENCE_AND,
|
|
24
|
+
or: PRECEDENCE_OR,
|
|
25
|
+
pipe: PRECEDENCE_PIPE,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function formatSubexpression(
|
|
29
|
+
expr: JsonSelector,
|
|
30
|
+
options: Partial<FormatJsonSelectorOptions>,
|
|
31
|
+
precedence: number
|
|
32
|
+
): string {
|
|
33
|
+
// Ensure @ is only used as a bare expression
|
|
34
|
+
if (!options.currentImplied) {
|
|
35
|
+
options = { ...options, currentImplied: true };
|
|
36
|
+
}
|
|
37
|
+
let result = formatJsonSelector(expr, options);
|
|
38
|
+
const subPrecedence = operatorPrecedence[expr.type];
|
|
39
|
+
if (subPrecedence != null && subPrecedence > precedence) {
|
|
40
|
+
result = `(${result})`;
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const projectionNodeTypes = new Set<JsonSelectorNodeType>([
|
|
46
|
+
"project",
|
|
47
|
+
"filter",
|
|
48
|
+
"slice",
|
|
49
|
+
"flatten",
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
export interface FormatJsonSelectorOptions {
|
|
53
|
+
currentImplied: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function formatJsonSelector(
|
|
57
|
+
selector: JsonSelector,
|
|
58
|
+
options: Partial<FormatJsonSelectorOptions> = {}
|
|
59
|
+
): string {
|
|
60
|
+
return visitJsonSelector<string, undefined>(
|
|
61
|
+
selector,
|
|
62
|
+
{
|
|
63
|
+
current() {
|
|
64
|
+
return !options.currentImplied ? "@" : "";
|
|
65
|
+
},
|
|
66
|
+
root() {
|
|
67
|
+
return "$";
|
|
68
|
+
},
|
|
69
|
+
literal({ value }) {
|
|
70
|
+
return formatLiteral(value);
|
|
71
|
+
},
|
|
72
|
+
identifier({ id }) {
|
|
73
|
+
return formatIdentifier(id);
|
|
74
|
+
},
|
|
75
|
+
fieldAccess({ expression, field }) {
|
|
76
|
+
const lv = formatSubexpression(expression, options, PRECEDENCE_ACCESS);
|
|
77
|
+
return `${lv}.${formatIdentifier(field)}`;
|
|
78
|
+
},
|
|
79
|
+
indexAccess({ expression, index }) {
|
|
80
|
+
const lv = formatSubexpression(expression, options, PRECEDENCE_ACCESS);
|
|
81
|
+
return `${lv}[${index}]`;
|
|
82
|
+
},
|
|
83
|
+
idAccess({ expression, id }) {
|
|
84
|
+
const lv = formatSubexpression(expression, options, PRECEDENCE_ACCESS);
|
|
85
|
+
return `${lv}[${formatRawString(id)}]`;
|
|
86
|
+
},
|
|
87
|
+
project({ expression, projection }) {
|
|
88
|
+
let result = formatSubexpression(
|
|
89
|
+
expression,
|
|
90
|
+
options,
|
|
91
|
+
PRECEDENCE_ACCESS
|
|
92
|
+
);
|
|
93
|
+
// Wildcard operator is only needed if expression is not already a projection
|
|
94
|
+
if (!projectionNodeTypes.has(expression.type)) {
|
|
95
|
+
result += "[*]";
|
|
96
|
+
}
|
|
97
|
+
if (projection) {
|
|
98
|
+
result += formatSubexpression(projection, options, PRECEDENCE_MAX);
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
},
|
|
102
|
+
filter({ expression, condition }) {
|
|
103
|
+
const lv = formatSubexpression(expression, options, PRECEDENCE_ACCESS);
|
|
104
|
+
const rv = formatSubexpression(condition, options, PRECEDENCE_MAX);
|
|
105
|
+
return `${lv}[?${rv}]`;
|
|
106
|
+
},
|
|
107
|
+
slice({ expression, start, end, step }) {
|
|
108
|
+
const lv = formatSubexpression(expression, options, PRECEDENCE_ACCESS);
|
|
109
|
+
const rv = `${start ?? ""}:${end ?? ""}${
|
|
110
|
+
step != null ? `:${step}` : ""
|
|
111
|
+
}`;
|
|
112
|
+
return `${lv}[${rv}]`;
|
|
113
|
+
},
|
|
114
|
+
flatten({ expression }) {
|
|
115
|
+
const lv = formatSubexpression(expression, options, PRECEDENCE_ACCESS);
|
|
116
|
+
return `${lv}[]`;
|
|
117
|
+
},
|
|
118
|
+
not({ expression }) {
|
|
119
|
+
return `!${formatSubexpression(expression, options, PRECEDENCE_NOT)}`;
|
|
120
|
+
},
|
|
121
|
+
compare({ lhs, operator, rhs }) {
|
|
122
|
+
const lv = formatSubexpression(lhs, options, PRECEDENCE_COMPARE);
|
|
123
|
+
const rv = formatSubexpression(rhs, options, PRECEDENCE_COMPARE - 1);
|
|
124
|
+
return `${lv} ${operator} ${rv}`;
|
|
125
|
+
},
|
|
126
|
+
and({ lhs, rhs }) {
|
|
127
|
+
const lv = formatSubexpression(lhs, options, PRECEDENCE_AND);
|
|
128
|
+
const rv = formatSubexpression(rhs, options, PRECEDENCE_AND - 1);
|
|
129
|
+
return `${lv} && ${rv}`;
|
|
130
|
+
},
|
|
131
|
+
or({ lhs, rhs }) {
|
|
132
|
+
const lv = formatSubexpression(lhs, options, PRECEDENCE_OR);
|
|
133
|
+
const rv = formatSubexpression(rhs, options, PRECEDENCE_OR - 1);
|
|
134
|
+
return `${lv} || ${rv}`;
|
|
135
|
+
},
|
|
136
|
+
pipe({ lhs, rhs }) {
|
|
137
|
+
const lv = formatSubexpression(lhs, options, PRECEDENCE_PIPE);
|
|
138
|
+
const rv = formatSubexpression(rhs, options, PRECEDENCE_PIPE - 1);
|
|
139
|
+
return `${lv} | ${rv}`;
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
undefined
|
|
143
|
+
);
|
|
144
|
+
}
|
package/src/get.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// Based on https://jmespath.org/specification.html#grammar
|
|
2
|
+
|
|
3
|
+
{
|
|
4
|
+
function binaryExpression(type, head, tail) {
|
|
5
|
+
return tail.reduce((lhs, rhs) => (
|
|
6
|
+
{
|
|
7
|
+
type,
|
|
8
|
+
lhs,
|
|
9
|
+
rhs
|
|
10
|
+
}
|
|
11
|
+
), head);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function reduceProjection(lhs, rhs) {
|
|
15
|
+
return rhs.reduce((result, fn) => fn(result), lhs);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function maybeProject(expression, pfns) {
|
|
19
|
+
return !pfns.length ? expression : (
|
|
20
|
+
{
|
|
21
|
+
type: "project",
|
|
22
|
+
expression: unwrapTrivialProjection(expression),
|
|
23
|
+
projection: pfns.reduce((result, pfn) => pfn(result), { type: "current" })
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function unwrapTrivialProjection(expression) {
|
|
28
|
+
const { type, projection } = expression;
|
|
29
|
+
return type === "project" && (!projection || projection.type === "current") ? expression.expression : expression;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
selector = ws @pipe_expression ws
|
|
34
|
+
|
|
35
|
+
pipe_expression = head:or_expression tail:(ws "|" ws @or_expression)*
|
|
36
|
+
{ return binaryExpression("pipe", head, tail); }
|
|
37
|
+
|
|
38
|
+
or_expression = head:and_expression tail:(ws "||" ws @and_expression)*
|
|
39
|
+
{ return binaryExpression("or", head, tail); }
|
|
40
|
+
|
|
41
|
+
and_expression = head:compare_expression tail:(ws "&&" ws @compare_expression)*
|
|
42
|
+
{ return binaryExpression("and", head, tail); }
|
|
43
|
+
|
|
44
|
+
compare_expression = head:not_expression tail:(ws @("<=" / ">=" / "<" / ">" / "==" / "!=") ws @not_expression)*
|
|
45
|
+
{
|
|
46
|
+
return tail.reduce((result, [operator, rhs]) => (
|
|
47
|
+
{
|
|
48
|
+
type: "compare",
|
|
49
|
+
operator,
|
|
50
|
+
lhs: result,
|
|
51
|
+
rhs
|
|
52
|
+
}
|
|
53
|
+
), head);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
not_expression =
|
|
57
|
+
"!" expression:not_expression { return { type: "not", expression }; }
|
|
58
|
+
/ flatten_expression
|
|
59
|
+
|
|
60
|
+
flatten_expression = lhs:flatten_lhs rhs:flatten_rhs* { return reduceProjection(lhs, rhs); }
|
|
61
|
+
|
|
62
|
+
flatten_lhs =
|
|
63
|
+
flatten:flatten { return flatten({ type: "current" }); }
|
|
64
|
+
/ filter_expression
|
|
65
|
+
|
|
66
|
+
flatten_rhs =
|
|
67
|
+
flatten:flatten pfns:filter_rhs* { return (expression) => maybeProject(flatten(expression), pfns); }
|
|
68
|
+
/ filter_rhs
|
|
69
|
+
|
|
70
|
+
flatten = ws "[]" { return (expression) => ({ type: "flatten", expression }); }
|
|
71
|
+
|
|
72
|
+
filter_expression = lhs:filter_lhs rhs:filter_rhs* { return reduceProjection(lhs, rhs); }
|
|
73
|
+
|
|
74
|
+
filter_lhs =
|
|
75
|
+
filter:filter { return filter({ type: "current" }); }
|
|
76
|
+
/ projection_expression
|
|
77
|
+
|
|
78
|
+
filter_rhs =
|
|
79
|
+
filter:filter pfns:filter_rhs* { return (expression) => maybeProject(filter(expression), pfns); }
|
|
80
|
+
/ projection_rhs
|
|
81
|
+
/ dot_rhs
|
|
82
|
+
|
|
83
|
+
filter = ws "[?" condition:selector "]" { return (expression) => ({ type: "filter", expression, condition }); }
|
|
84
|
+
|
|
85
|
+
projection_expression = lhs:projection_lhs rhs:projection_rhs* { return reduceProjection(lhs, rhs); }
|
|
86
|
+
|
|
87
|
+
projection_lhs =
|
|
88
|
+
projection:projection { return projection({ type: "current" }); }
|
|
89
|
+
/ index_expression
|
|
90
|
+
|
|
91
|
+
projection_rhs =
|
|
92
|
+
projection:projection pfns:filter_rhs* { return (expression) => maybeProject(projection(expression), pfns); }
|
|
93
|
+
/ index_rhs
|
|
94
|
+
|
|
95
|
+
projection =
|
|
96
|
+
ws "[" ws "*" ws "]" { return (expression) => ({ type: "project", expression, projection: { type: "current" } }); }
|
|
97
|
+
/ ws "[" ws @slice ws "]"
|
|
98
|
+
|
|
99
|
+
slice = start:number? ws ":" ws end:number? ws ":"? ws step:number?
|
|
100
|
+
{
|
|
101
|
+
return (expression) => ({
|
|
102
|
+
type: "slice",
|
|
103
|
+
expression,
|
|
104
|
+
start: start ?? undefined,
|
|
105
|
+
end: end ?? undefined,
|
|
106
|
+
step: step ?? undefined,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
index_expression = lhs:index_lhs rhs:index_rhs* { return reduceProjection(lhs, rhs); }
|
|
111
|
+
|
|
112
|
+
index_lhs =
|
|
113
|
+
index:index_rhs { return index({ type: "current" }); }
|
|
114
|
+
/ member_expression
|
|
115
|
+
|
|
116
|
+
index_rhs =
|
|
117
|
+
ws "[" ws index:number ws "]" { return (expression) => ({ type: "indexAccess", expression, index }); }
|
|
118
|
+
/ ws "[" ws id:raw_string ws "]" { return (expression) => ({ type: "idAccess", expression, id }); }
|
|
119
|
+
|
|
120
|
+
dot_rhs = ws "." ws field:identifier
|
|
121
|
+
{ return (expression) => ({ type: "fieldAccess", expression, field }); }
|
|
122
|
+
|
|
123
|
+
member_expression = lhs:primary_expression rhs:dot_rhs* { return reduceProjection(lhs, rhs); }
|
|
124
|
+
|
|
125
|
+
primary_expression =
|
|
126
|
+
id:identifier { return { type: "identifier", id }; }
|
|
127
|
+
/ "@" { return { type: "current" }; }
|
|
128
|
+
/ "$" { return { type: "root" }; }
|
|
129
|
+
/ literal
|
|
130
|
+
/ value:raw_string { return { type: "literal", value }; }
|
|
131
|
+
/ "(" @selector ")"
|
|
132
|
+
|
|
133
|
+
literal = "`" ws value:(json_value / unquoted_json_string) ws "`"
|
|
134
|
+
{ return { type: "literal", value }; }
|
|
135
|
+
|
|
136
|
+
// Identifiers and double-quoted strings
|
|
137
|
+
|
|
138
|
+
identifier = unquoted_string / quoted_string
|
|
139
|
+
|
|
140
|
+
unquoted_string = head:[a-z_]i tail:[0-9a-z_]i* { return head + tail.join(""); }
|
|
141
|
+
|
|
142
|
+
quoted_string = '"' chars:char* '"' { return chars.join(""); }
|
|
143
|
+
|
|
144
|
+
char = unescaped_char / escaped_char
|
|
145
|
+
|
|
146
|
+
unescaped_char = [^\0-\x1F"\\]
|
|
147
|
+
|
|
148
|
+
escaped_char = "\\" @(
|
|
149
|
+
'"'
|
|
150
|
+
/ "\\"
|
|
151
|
+
/ "/"
|
|
152
|
+
/ "b" { return "\b"; }
|
|
153
|
+
/ "f" { return "\f"; }
|
|
154
|
+
/ "n" { return "\n"; }
|
|
155
|
+
/ "r" { return "\r"; }
|
|
156
|
+
/ "t" { return "\t"; }
|
|
157
|
+
/ "u" digits:$(HEXDIG HEXDIG HEXDIG HEXDIG) {
|
|
158
|
+
return String.fromCharCode(parseInt(digits, 16));
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
HEXDIG = [0-9a-f]i
|
|
163
|
+
|
|
164
|
+
// Raw strings (single-quoted)
|
|
165
|
+
|
|
166
|
+
raw_string = "'" chars:raw_string_char* "'" { return chars.join(""); }
|
|
167
|
+
|
|
168
|
+
raw_string_char = unescaped_raw_string_char / preserved_escape / raw_string_escape
|
|
169
|
+
|
|
170
|
+
// Despite what the specification says, JMESPath implementations accept control characters
|
|
171
|
+
unescaped_raw_string_char = [^'\\]
|
|
172
|
+
|
|
173
|
+
preserved_escape = "\\" [^'] { return text(); }
|
|
174
|
+
|
|
175
|
+
raw_string_escape = "\\" @"'"
|
|
176
|
+
|
|
177
|
+
// Numbers
|
|
178
|
+
|
|
179
|
+
number = int { return parseInt(text()); }
|
|
180
|
+
|
|
181
|
+
int = "-"? ("0" / ([1-9] [0-9]*))
|
|
182
|
+
|
|
183
|
+
// JSON
|
|
184
|
+
|
|
185
|
+
json_value =
|
|
186
|
+
"null" { return null; }
|
|
187
|
+
/ "false" { return false; }
|
|
188
|
+
/ "true" { return true; }
|
|
189
|
+
/ json_number
|
|
190
|
+
/ json_string
|
|
191
|
+
/ json_object
|
|
192
|
+
/ json_array
|
|
193
|
+
|
|
194
|
+
json_number = int ("." [0-9]+)? ([eE] [+-]? [0-9]+)? { return parseFloat(text()); }
|
|
195
|
+
|
|
196
|
+
json_string = '"' @unquoted_json_string '"'
|
|
197
|
+
|
|
198
|
+
unquoted_json_string = chars:(unescaped_literal / escaped_literal)* { return chars.join(""); }
|
|
199
|
+
|
|
200
|
+
unescaped_literal = [^\0-\x1F"\\`]
|
|
201
|
+
|
|
202
|
+
escaped_literal = escaped_char / "\\" @"`"
|
|
203
|
+
|
|
204
|
+
json_object =
|
|
205
|
+
"{" ws members:(
|
|
206
|
+
head:json_member
|
|
207
|
+
tail:(ws "," ws @json_member)*
|
|
208
|
+
{ return [head].concat(tail); }
|
|
209
|
+
)?
|
|
210
|
+
ws "}"
|
|
211
|
+
{ return Object.fromEntries(members ?? []); }
|
|
212
|
+
|
|
213
|
+
json_member = name:json_string ws ":" ws value:json_value
|
|
214
|
+
{ return [name, value]; }
|
|
215
|
+
|
|
216
|
+
json_array =
|
|
217
|
+
"[" ws values:(
|
|
218
|
+
head:json_value
|
|
219
|
+
tail:(ws "," ws @json_value)*
|
|
220
|
+
{ return [head].concat(tail); }
|
|
221
|
+
)? ws "]"
|
|
222
|
+
{ return values ?? []; }
|
|
223
|
+
|
|
224
|
+
// Whitespace
|
|
225
|
+
|
|
226
|
+
ws "whitespace" = [ \t\n\r]*
|
package/src/index.ts
ADDED
package/src/parse.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { JsonSelector } from "./ast";
|
|
2
|
+
import { parse } from "./__generated__/parser";
|
|
3
|
+
|
|
4
|
+
export function parseJsonSelector(selectorExpression: string): JsonSelector {
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
6
|
+
return parse(selectorExpression);
|
|
7
|
+
}
|
package/src/set.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { accessWithJsonSelector } from "./access";
|
|
2
|
+
import { JsonSelector } from "./ast";
|
|
3
|
+
|
|
4
|
+
export function setWithJsonSelector(
|
|
5
|
+
selector: JsonSelector,
|
|
6
|
+
context: unknown,
|
|
7
|
+
value: unknown
|
|
8
|
+
): unknown {
|
|
9
|
+
const accessor = accessWithJsonSelector(selector, context);
|
|
10
|
+
const oldValue = accessor.get();
|
|
11
|
+
accessor.set(value);
|
|
12
|
+
return oldValue;
|
|
13
|
+
}
|