@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.
Files changed (50) hide show
  1. package/README.md +50 -3
  2. package/dist/__generated__/parser.d.ts.map +1 -1
  3. package/dist/access.d.ts +6 -6
  4. package/dist/access.d.ts.map +1 -1
  5. package/dist/ast.d.ts +6 -3
  6. package/dist/ast.d.ts.map +1 -1
  7. package/dist/evaluate.d.ts +5 -5
  8. package/dist/evaluate.d.ts.map +1 -1
  9. package/dist/format.d.ts.map +1 -1
  10. package/dist/json-selector.esm.js +3510 -0
  11. package/dist/json-selector.esm.js.map +1 -0
  12. package/dist/json-selector.umd.js +3575 -0
  13. package/dist/json-selector.umd.js.map +1 -0
  14. package/dist/visitor.d.ts +2 -1
  15. package/dist/visitor.d.ts.map +1 -1
  16. package/package.json +32 -20
  17. package/src/__generated__/parser.js +2730 -0
  18. package/src/access.ts +534 -0
  19. package/src/ast.ts +114 -0
  20. package/src/evaluate.ts +269 -0
  21. package/src/format.ts +144 -0
  22. package/src/get.ts +9 -0
  23. package/src/grammar.pegjs +226 -0
  24. package/src/index.ts +7 -0
  25. package/src/parse.ts +7 -0
  26. package/src/set.ts +13 -0
  27. package/src/util.ts +70 -0
  28. package/src/visitor.ts +79 -0
  29. package/dist/__generated__/parser.js +0 -2855
  30. package/dist/__generated__/parser.js.map +0 -1
  31. package/dist/access.js +0 -444
  32. package/dist/access.js.map +0 -1
  33. package/dist/ast.js +0 -3
  34. package/dist/ast.js.map +0 -1
  35. package/dist/evaluate.js +0 -168
  36. package/dist/evaluate.js.map +0 -1
  37. package/dist/format.js +0 -119
  38. package/dist/format.js.map +0 -1
  39. package/dist/get.js +0 -9
  40. package/dist/get.js.map +0 -1
  41. package/dist/index.js +0 -26
  42. package/dist/index.js.map +0 -1
  43. package/dist/parse.js +0 -10
  44. package/dist/parse.js.map +0 -1
  45. package/dist/set.js +0 -12
  46. package/dist/set.js.map +0 -1
  47. package/dist/util.js +0 -70
  48. package/dist/util.js.map +0 -1
  49. package/dist/visitor.js +0 -39
  50. package/dist/visitor.js.map +0 -1
@@ -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,9 @@
1
+ import { accessWithJsonSelector } from "./access";
2
+ import { JsonSelector } from "./ast";
3
+
4
+ export function getWithJsonSelector(
5
+ selector: JsonSelector,
6
+ context: unknown
7
+ ): unknown {
8
+ return accessWithJsonSelector(selector, context).get();
9
+ }
@@ -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
@@ -0,0 +1,7 @@
1
+ export * from "./access";
2
+ export * from "./ast";
3
+ export { evaluateJsonSelector } from "./evaluate";
4
+ export * from "./format";
5
+ export * from "./get";
6
+ export * from "./parse";
7
+ export * from "./set";
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
+ }