@loancrate/json-selector 2.0.0 → 2.1.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/src/access.ts ADDED
@@ -0,0 +1,510 @@
1
+ import { JsonSelector } from "./ast";
2
+ import {
3
+ compare,
4
+ evaluateJsonSelector,
5
+ filter,
6
+ flatten,
7
+ normalizeSlice,
8
+ project,
9
+ slice,
10
+ } from "./evaluate";
11
+ import { formatJsonSelector } from "./format";
12
+ import {
13
+ asArray,
14
+ findId,
15
+ findIdIndex,
16
+ getField,
17
+ getIndex,
18
+ isArray,
19
+ isFalseOrEmpty,
20
+ isObject,
21
+ } from "./util";
22
+ import { visitJsonSelector } from "./visitor";
23
+
24
+ export interface UnboundAccessor {
25
+ readonly selector: JsonSelector;
26
+ isValidContext(context: unknown): boolean;
27
+ get(context: unknown): unknown;
28
+ set(context: unknown, value: unknown): void;
29
+ delete(context: unknown): void;
30
+ }
31
+
32
+ abstract class BaseAccessor implements UnboundAccessor {
33
+ constructor(readonly selector: JsonSelector) {}
34
+ abstract isValidContext(context: unknown): boolean;
35
+ abstract get(context: unknown): unknown;
36
+ abstract set(context: unknown, value: unknown): void;
37
+ abstract delete(context: unknown): void;
38
+ }
39
+
40
+ abstract class ReadOnlyAccessor extends BaseAccessor {
41
+ constructor(selector: JsonSelector) {
42
+ super(selector);
43
+ }
44
+
45
+ isValidContext(): boolean {
46
+ return true;
47
+ }
48
+
49
+ set(): void {
50
+ // ignored
51
+ }
52
+
53
+ delete(): void {
54
+ // ignored
55
+ }
56
+ }
57
+
58
+ class ConstantAccessor<T> extends ReadOnlyAccessor {
59
+ constructor(selector: JsonSelector, readonly value: T) {
60
+ super(selector);
61
+ }
62
+
63
+ get(): T {
64
+ return this.value;
65
+ }
66
+ }
67
+
68
+ class ContextAccessor extends ReadOnlyAccessor {
69
+ constructor() {
70
+ super({ type: "current" });
71
+ }
72
+
73
+ get(context: unknown): unknown {
74
+ return context;
75
+ }
76
+ }
77
+
78
+ export function makeJsonSelectorAccessor(
79
+ selector: JsonSelector
80
+ ): UnboundAccessor {
81
+ return visitJsonSelector<UnboundAccessor, undefined>(
82
+ selector,
83
+ {
84
+ current() {
85
+ return new ContextAccessor();
86
+ },
87
+ literal(selector) {
88
+ return new ConstantAccessor(selector, selector.value);
89
+ },
90
+ identifier(selector) {
91
+ const { id } = selector;
92
+ const Accessor = class extends BaseAccessor {
93
+ constructor() {
94
+ super(selector);
95
+ }
96
+ isValidContext(context: unknown) {
97
+ return isObject(context);
98
+ }
99
+ get(context: unknown) {
100
+ return getField(context, id);
101
+ }
102
+ set(context: unknown, value: unknown) {
103
+ if (isObject(context)) {
104
+ context[id] = value;
105
+ }
106
+ }
107
+ delete(context: unknown) {
108
+ if (isObject(context)) {
109
+ delete context[id];
110
+ }
111
+ }
112
+ };
113
+ return new Accessor();
114
+ },
115
+ fieldAccess(selector) {
116
+ const { expression, field } = selector;
117
+ const base = makeJsonSelectorAccessor(expression);
118
+ const Accessor = class extends BaseAccessor {
119
+ constructor() {
120
+ super(selector);
121
+ }
122
+ isValidContext(context: unknown) {
123
+ return isObject(base.get(context));
124
+ }
125
+ get(context: unknown) {
126
+ return getField(base.get(context), field);
127
+ }
128
+ set(context: unknown, value: unknown) {
129
+ const obj = base.get(context);
130
+ if (isObject(obj)) {
131
+ obj[field] = value;
132
+ }
133
+ }
134
+ delete(context: unknown) {
135
+ const obj = base.get(context);
136
+ if (isObject(obj)) {
137
+ delete obj[field];
138
+ }
139
+ }
140
+ };
141
+ return new Accessor();
142
+ },
143
+ indexAccess(selector) {
144
+ const { expression, index } = selector;
145
+ const base = makeJsonSelectorAccessor(expression);
146
+ const Accessor = class extends BaseAccessor {
147
+ constructor() {
148
+ super(selector);
149
+ }
150
+ isValidContext(context: unknown) {
151
+ return isArray(base.get(context));
152
+ }
153
+ get(context: unknown) {
154
+ return getIndex(base.get(context), index);
155
+ }
156
+ set(context: unknown, value: unknown) {
157
+ const arr = base.get(context);
158
+ if (isArray(arr)) {
159
+ arr[index] = value;
160
+ }
161
+ }
162
+ delete(context: unknown) {
163
+ const arr = base.get(context);
164
+ if (isArray(arr)) {
165
+ arr.splice(index, 1);
166
+ }
167
+ }
168
+ };
169
+ return new Accessor();
170
+ },
171
+ idAccess(selector) {
172
+ const { expression, id } = selector;
173
+ const base = makeJsonSelectorAccessor(expression);
174
+ const Accessor = class extends BaseAccessor {
175
+ constructor() {
176
+ super(selector);
177
+ }
178
+ isValidContext(context: unknown) {
179
+ return isArray(base.get(context));
180
+ }
181
+ get(context: unknown) {
182
+ return findId(base.get(context), id);
183
+ }
184
+ set(context: unknown, value: unknown) {
185
+ const arr = base.get(context);
186
+ if (isArray(arr)) {
187
+ const index = findIdIndex(arr, id);
188
+ if (index >= 0) {
189
+ arr[index] = value;
190
+ }
191
+ }
192
+ }
193
+ delete(context: unknown) {
194
+ const arr = base.get(context);
195
+ if (isArray(arr)) {
196
+ const index = findIdIndex(arr, id);
197
+ if (index >= 0) {
198
+ arr.splice(index, 1);
199
+ }
200
+ }
201
+ }
202
+ };
203
+ return new Accessor();
204
+ },
205
+ project(selector) {
206
+ const { expression, projection } = selector;
207
+ const base = makeJsonSelectorAccessor(expression);
208
+ const proj = projection && makeJsonSelectorAccessor(projection);
209
+ const Accessor = class extends BaseAccessor {
210
+ constructor() {
211
+ super(selector);
212
+ }
213
+ isValidContext(context: unknown) {
214
+ return isArray(base.get(context));
215
+ }
216
+ get(context: unknown) {
217
+ return project(base.get(context), projection);
218
+ }
219
+ set(context: unknown, value: unknown) {
220
+ const arr = base.get(context);
221
+ if (isArray(arr)) {
222
+ if (proj) {
223
+ for (const element of arr) {
224
+ proj.set(element, value);
225
+ }
226
+ } else {
227
+ replaceArray(arr, asArray(value));
228
+ }
229
+ }
230
+ }
231
+ delete(context: unknown) {
232
+ const arr = base.get(context);
233
+ if (isArray(arr)) {
234
+ if (proj) {
235
+ for (const element of arr) {
236
+ proj.delete(element);
237
+ }
238
+ } else {
239
+ arr.length = 0;
240
+ }
241
+ }
242
+ }
243
+ };
244
+ return new Accessor();
245
+ },
246
+ filter(selector) {
247
+ const { expression, condition } = selector;
248
+ const base = makeJsonSelectorAccessor(expression);
249
+ const Accessor = class extends BaseAccessor {
250
+ constructor() {
251
+ super(selector);
252
+ }
253
+ isValidContext(context: unknown) {
254
+ return isArray(base.get(context));
255
+ }
256
+ get(context: unknown) {
257
+ return filter(base.get(context), condition);
258
+ }
259
+ set(context: unknown, value: unknown) {
260
+ const arr = base.get(context);
261
+ if (isArray(arr)) {
262
+ replaceArray(
263
+ arr,
264
+ invertedFilter(arr, condition).concat(asArray(value))
265
+ );
266
+ }
267
+ }
268
+ delete(context: unknown) {
269
+ const arr = base.get(context);
270
+ if (isArray(arr)) {
271
+ replaceArray(arr, invertedFilter(arr, condition));
272
+ }
273
+ }
274
+ };
275
+ return new Accessor();
276
+ },
277
+ slice(selector) {
278
+ const { expression, start, end, step } = selector;
279
+ const base = makeJsonSelectorAccessor(expression);
280
+ const Accessor = class extends BaseAccessor {
281
+ constructor() {
282
+ super(selector);
283
+ }
284
+ isValidContext(context: unknown) {
285
+ return isArray(base.get(context));
286
+ }
287
+ get(context: unknown) {
288
+ return slice(base.get(context), start, end, step);
289
+ }
290
+ set(context: unknown, value: unknown) {
291
+ const arr = base.get(context);
292
+ if (isArray(arr)) {
293
+ replaceArray(
294
+ arr,
295
+ invertedSlice(arr, start, end, step).concat(asArray(value))
296
+ );
297
+ }
298
+ }
299
+ delete(context: unknown) {
300
+ const arr = base.get(context);
301
+ if (isArray(arr)) {
302
+ replaceArray(arr, invertedSlice(arr, start, end, step));
303
+ }
304
+ }
305
+ };
306
+ return new Accessor();
307
+ },
308
+ flatten(selector) {
309
+ const { expression } = selector;
310
+ const base = makeJsonSelectorAccessor(expression);
311
+ const Accessor = class extends BaseAccessor {
312
+ constructor() {
313
+ super(selector);
314
+ }
315
+ isValidContext(context: unknown) {
316
+ return isArray(base.get(context));
317
+ }
318
+ get(context: unknown) {
319
+ return flatten(base.get(context));
320
+ }
321
+ set(context: unknown, value: unknown) {
322
+ const arr = base.get(context);
323
+ if (isArray(arr)) {
324
+ replaceArray(arr, asArray(value));
325
+ }
326
+ }
327
+ delete(context: unknown) {
328
+ const arr = base.get(context);
329
+ if (isArray(arr)) {
330
+ arr.length = 0;
331
+ }
332
+ }
333
+ };
334
+ return new Accessor();
335
+ },
336
+ not(selector) {
337
+ const { expression } = selector;
338
+ const base = makeJsonSelectorAccessor(expression);
339
+ const Accessor = class extends ReadOnlyAccessor {
340
+ constructor() {
341
+ super(selector);
342
+ }
343
+ get(context: unknown) {
344
+ return isFalseOrEmpty(base.get(context));
345
+ }
346
+ };
347
+ return new Accessor();
348
+ },
349
+ compare(selector) {
350
+ const { lhs, rhs, operator } = selector;
351
+ const la = makeJsonSelectorAccessor(lhs);
352
+ const ra = makeJsonSelectorAccessor(rhs);
353
+ const Accessor = class extends ReadOnlyAccessor {
354
+ constructor() {
355
+ super(selector);
356
+ }
357
+ get(context: unknown) {
358
+ const lv = la.get(context);
359
+ const rv = ra.get(context);
360
+ return compare(lv, rv, operator);
361
+ }
362
+ };
363
+ return new Accessor();
364
+ },
365
+ and(selector) {
366
+ const { lhs, rhs } = selector;
367
+ const la = makeJsonSelectorAccessor(lhs);
368
+ const ra = makeJsonSelectorAccessor(rhs);
369
+ const Accessor = class extends ReadOnlyAccessor {
370
+ constructor() {
371
+ super(selector);
372
+ }
373
+ get(context: unknown) {
374
+ const lv = la.get(context);
375
+ return isFalseOrEmpty(lv) ? lv : ra.get(context);
376
+ }
377
+ };
378
+ return new Accessor();
379
+ },
380
+ or(selector) {
381
+ const { lhs, rhs } = selector;
382
+ const la = makeJsonSelectorAccessor(lhs);
383
+ const ra = makeJsonSelectorAccessor(rhs);
384
+ const Accessor = class extends ReadOnlyAccessor {
385
+ constructor() {
386
+ super(selector);
387
+ }
388
+ get(context: unknown) {
389
+ const lv = la.get(context);
390
+ return !isFalseOrEmpty(lv) ? lv : ra.get(context);
391
+ }
392
+ };
393
+ return new Accessor();
394
+ },
395
+ pipe(selector) {
396
+ const { lhs, rhs } = selector;
397
+ const la = makeJsonSelectorAccessor(lhs);
398
+ const ra = makeJsonSelectorAccessor(rhs);
399
+ const Accessor = class extends BaseAccessor {
400
+ constructor() {
401
+ super(selector);
402
+ }
403
+ isValidContext(context: unknown) {
404
+ return ra.isValidContext(la.get(context));
405
+ }
406
+ get(context: unknown) {
407
+ return ra.get(la.get(context));
408
+ }
409
+ set(context: unknown, value: unknown) {
410
+ ra.set(la.get(context), value);
411
+ }
412
+ delete(context: unknown) {
413
+ ra.delete(la.get(context));
414
+ }
415
+ };
416
+ return new Accessor();
417
+ },
418
+ },
419
+ undefined
420
+ );
421
+ }
422
+
423
+ export interface Accessor<T> {
424
+ readonly selector: JsonSelector;
425
+ readonly valid: boolean;
426
+ readonly path: string;
427
+ get(): T;
428
+ set(value: T): void;
429
+ delete(): void;
430
+ }
431
+
432
+ export function bindJsonSelectorAccessor(
433
+ unbound: UnboundAccessor,
434
+ context: unknown
435
+ ): Accessor<unknown> {
436
+ const { selector } = unbound;
437
+ const valid = unbound.isValidContext(context);
438
+ return {
439
+ selector,
440
+ valid,
441
+ path: formatJsonSelector(selector),
442
+ get() {
443
+ return unbound.get(context);
444
+ },
445
+ set(v: unknown) {
446
+ unbound.set(context, v);
447
+ },
448
+ delete() {
449
+ unbound.delete(context);
450
+ },
451
+ };
452
+ }
453
+
454
+ export function accessWithJsonSelector(
455
+ selector: JsonSelector,
456
+ context: unknown
457
+ ): Accessor<unknown> {
458
+ return bindJsonSelectorAccessor(makeJsonSelectorAccessor(selector), context);
459
+ }
460
+
461
+ function replaceArray(
462
+ target: unknown[],
463
+ source: readonly unknown[]
464
+ ): unknown[] {
465
+ target.length = 0;
466
+ target.push(...source);
467
+ return target;
468
+ }
469
+
470
+ function invertedFilter(value: unknown[], condition: JsonSelector): unknown[] {
471
+ return value.filter((e) =>
472
+ isFalseOrEmpty(evaluateJsonSelector(condition, e))
473
+ );
474
+ }
475
+
476
+ export function invertedSlice(
477
+ value: unknown[],
478
+ start: number | undefined,
479
+ end?: number,
480
+ step?: number
481
+ ): unknown[] {
482
+ ({ start, end, step } = normalizeSlice(value.length, start, end, step));
483
+ const collected: unknown[] = [];
484
+ if (step > 0) {
485
+ if (start >= end) {
486
+ return value;
487
+ }
488
+ let skip = start;
489
+ for (let i = 0; i < value.length; ++i) {
490
+ if (i < skip || i >= end) {
491
+ collected.push(value[i]);
492
+ } else {
493
+ skip += step;
494
+ }
495
+ }
496
+ } else {
497
+ if (start <= end) {
498
+ return value;
499
+ }
500
+ let skip = start;
501
+ for (let i = value.length - 1; i >= 0; --i) {
502
+ if (i > skip || i <= end) {
503
+ collected.push(value[i]);
504
+ } else {
505
+ skip += step;
506
+ }
507
+ }
508
+ }
509
+ return collected;
510
+ }
package/src/ast.ts ADDED
@@ -0,0 +1,109 @@
1
+ import { JsonValue } from "type-fest";
2
+
3
+ export type JsonSelectorNodeType = JsonSelector["type"];
4
+
5
+ export interface JsonSelectorCurrent {
6
+ type: "current";
7
+ }
8
+
9
+ export interface JsonSelectorLiteral {
10
+ type: "literal";
11
+ value: JsonValue;
12
+ }
13
+
14
+ export interface JsonSelectorIdentifier {
15
+ type: "identifier";
16
+ id: string;
17
+ }
18
+
19
+ export interface JsonSelectorFieldAccess {
20
+ type: "fieldAccess";
21
+ expression: JsonSelector;
22
+ field: string;
23
+ }
24
+
25
+ export interface JsonSelectorIndexAccess {
26
+ type: "indexAccess";
27
+ expression: JsonSelector;
28
+ index: number;
29
+ }
30
+
31
+ export interface JsonSelectorIdAccess {
32
+ type: "idAccess";
33
+ expression: JsonSelector;
34
+ id: string;
35
+ }
36
+
37
+ export interface JsonSelectorProject {
38
+ type: "project";
39
+ expression: JsonSelector;
40
+ projection?: JsonSelector;
41
+ }
42
+
43
+ export interface JsonSelectorFilter {
44
+ type: "filter";
45
+ expression: JsonSelector;
46
+ condition: JsonSelector;
47
+ }
48
+
49
+ export interface JsonSelectorSlice {
50
+ type: "slice";
51
+ expression: JsonSelector;
52
+ start?: number;
53
+ end?: number;
54
+ step?: number;
55
+ }
56
+
57
+ export interface JsonSelectorFlatten {
58
+ type: "flatten";
59
+ expression: JsonSelector;
60
+ }
61
+
62
+ export interface JsonSelectorNot {
63
+ type: "not";
64
+ expression: JsonSelector;
65
+ }
66
+
67
+ export type JsonSelectorCompareOperator = "<" | "<=" | "==" | ">=" | ">" | "!=";
68
+
69
+ export interface JsonSelectorCompare {
70
+ type: "compare";
71
+ operator: JsonSelectorCompareOperator;
72
+ lhs: JsonSelector;
73
+ rhs: JsonSelector;
74
+ }
75
+
76
+ export interface JsonSelectorAnd {
77
+ type: "and";
78
+ lhs: JsonSelector;
79
+ rhs: JsonSelector;
80
+ }
81
+
82
+ export interface JsonSelectorOr {
83
+ type: "or";
84
+ lhs: JsonSelector;
85
+ rhs: JsonSelector;
86
+ }
87
+
88
+ export interface JsonSelectorPipe {
89
+ type: "pipe";
90
+ lhs: JsonSelector;
91
+ rhs: JsonSelector;
92
+ }
93
+
94
+ export type JsonSelector =
95
+ | JsonSelectorCurrent
96
+ | JsonSelectorLiteral
97
+ | JsonSelectorIdentifier
98
+ | JsonSelectorFieldAccess
99
+ | JsonSelectorIndexAccess
100
+ | JsonSelectorIdAccess
101
+ | JsonSelectorProject
102
+ | JsonSelectorFilter
103
+ | JsonSelectorSlice
104
+ | JsonSelectorFlatten
105
+ | JsonSelectorNot
106
+ | JsonSelectorCompare
107
+ | JsonSelectorAnd
108
+ | JsonSelectorOr
109
+ | JsonSelectorPipe;