json-web-streams 1.1.0 → 1.2.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 +83 -120
- package/dist/JSONParseStream.js +26 -57
- package/dist/JSONParseStreamRaw.d.ts +19 -6
- package/dist/JSONParseStreamRaw.js +181 -78
- package/dist/jsonPathToPathArray.js +5 -4
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -35,16 +35,14 @@ import { JSONParseStream } from "json-web-streams";
|
|
|
35
35
|
|
|
36
36
|
// data.json contains: [{ "x": 1 }, { "x": 2 }, { "x": 3 }]
|
|
37
37
|
const response = await fetch("https://example.com/data.json");
|
|
38
|
-
|
|
38
|
+
|
|
39
|
+
const stream = response.body
|
|
39
40
|
.pipeThrough(new TextDecoderStream())
|
|
40
|
-
.pipeThrough(new JSONParseStream(["$[*]"]))
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
},
|
|
46
|
-
}),
|
|
47
|
-
);
|
|
41
|
+
.pipeThrough(new JSONParseStream(["$[*]"]));
|
|
42
|
+
|
|
43
|
+
for await (const { value } of stream) {
|
|
44
|
+
console.log(value);
|
|
45
|
+
}
|
|
48
46
|
|
|
49
47
|
// Output:
|
|
50
48
|
// {"x": 1}
|
|
@@ -52,18 +50,6 @@ await response.body
|
|
|
52
50
|
// {"x": 3}
|
|
53
51
|
```
|
|
54
52
|
|
|
55
|
-
> [!TIP]
|
|
56
|
-
> If you don't have to support Safari, [most other environments](https://caniuse.com/mdn-api_readablestream_--asynciterator) let you use a nicer syntax for consuming stream output as an async iterator:
|
|
57
|
-
>
|
|
58
|
-
> ```ts
|
|
59
|
-
> const stream = response.body
|
|
60
|
-
> .pipeThrough(new TextDecoderStream())
|
|
61
|
-
> .pipeThrough(new JSONParseStream(["$[*]"]));
|
|
62
|
-
> for await (const { value } of stream) {
|
|
63
|
-
> console.log(value);
|
|
64
|
-
> }
|
|
65
|
-
> ```
|
|
66
|
-
|
|
67
53
|
## API
|
|
68
54
|
|
|
69
55
|
```ts
|
|
@@ -158,68 +144,60 @@ If you only have one JSONPath query, you can ignore `key`. But if you have more
|
|
|
158
144
|
|
|
159
145
|
<!-- prettier-ignore -->
|
|
160
146
|
```ts
|
|
161
|
-
|
|
147
|
+
const stream = new ReadableStream({
|
|
162
148
|
start(controller) {
|
|
163
149
|
controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
|
|
164
150
|
controller.close();
|
|
165
151
|
},
|
|
166
152
|
})
|
|
167
|
-
.pipeThrough(new JSONParseStream(["$.bar[*]", "$.foo[*]"]))
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
},
|
|
177
|
-
}),
|
|
178
|
-
);
|
|
153
|
+
.pipeThrough(new JSONParseStream(["$.bar[*]", "$.foo[*]"]));
|
|
154
|
+
|
|
155
|
+
for await (const record of stream) {
|
|
156
|
+
if (record.key === "$.bar[*]") {
|
|
157
|
+
// Do something with the values from bar
|
|
158
|
+
} else {
|
|
159
|
+
// Do something with the values from foo
|
|
160
|
+
}
|
|
161
|
+
}
|
|
179
162
|
```
|
|
180
163
|
|
|
181
164
|
Or with a manually defined key:
|
|
182
165
|
|
|
183
166
|
<!-- prettier-ignore -->
|
|
184
167
|
```ts
|
|
185
|
-
|
|
168
|
+
const stream = new ReadableStream({
|
|
186
169
|
start(controller) {
|
|
187
170
|
controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
|
|
188
171
|
controller.close();
|
|
189
172
|
},
|
|
190
173
|
})
|
|
191
|
-
.pipeThrough(new JSONParseStream([{ key: "bar", path: "$.bar[*]" }, "$.foo[*]"]))
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
},
|
|
201
|
-
}),
|
|
202
|
-
);
|
|
174
|
+
.pipeThrough(new JSONParseStream([{ key: "bar", path: "$.bar[*]" }, "$.foo[*]"]));
|
|
175
|
+
|
|
176
|
+
for await (const record of stream) {
|
|
177
|
+
if (record.key === "bar") {
|
|
178
|
+
// Do something with the values from bar
|
|
179
|
+
} else {
|
|
180
|
+
// Do something with the values from foo
|
|
181
|
+
}
|
|
182
|
+
}
|
|
203
183
|
```
|
|
204
184
|
|
|
205
185
|
`wildcardKeys` is defined when you have a wildcard in an object (not an array) somewhere in your JSONPath. For example:
|
|
206
186
|
|
|
207
187
|
<!-- prettier-ignore -->
|
|
208
188
|
```ts
|
|
209
|
-
|
|
189
|
+
const stream = new ReadableStream({
|
|
210
190
|
start(controller) {
|
|
211
191
|
controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
|
|
212
192
|
controller.close();
|
|
213
193
|
},
|
|
214
194
|
})
|
|
215
|
-
.pipeThrough(new JSONParseStream(["$[*]"]))
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}),
|
|
222
|
-
);
|
|
195
|
+
.pipeThrough(new JSONParseStream(["$[*]"]));
|
|
196
|
+
|
|
197
|
+
for await (const record of stream) {
|
|
198
|
+
console.log(record);
|
|
199
|
+
}
|
|
200
|
+
|
|
223
201
|
// Output:
|
|
224
202
|
// { key: "$[*]", value: [1, 2], wildcardKeys: ["foo"] },
|
|
225
203
|
// { key: "$[*]", value: ["a", "b", "c"], wildcardKeys: ["bar"] },
|
|
@@ -230,7 +208,7 @@ The purpose of `wildcardKeys` is to allow you to easily distinguish different ty
|
|
|
230
208
|
> [!WARNING]
|
|
231
209
|
> It is possible to have two JSONPath queries that output overlapping objects, like if your data is `{ "foo": [1, 2] }` and you query for both `$` and `$.foo`. This will emit two objects: `{ foo: [1, 2] }` and `[1, 2]`. Due to how json-web-streams works internally, both of those objects share the same array instance, meaning that if the array in one is mutated it will affect the other.
|
|
232
210
|
>
|
|
233
|
-
> Some schema validation libraries do a deep clone of objects they validate. In that case, you won't have this issue.
|
|
211
|
+
> Some schema validation libraries do a deep clone of objects they validate. In that case, you won't have this issue.
|
|
234
212
|
|
|
235
213
|
#### Schema validation and types for `JSONParseStream` output
|
|
236
214
|
|
|
@@ -242,7 +220,7 @@ To use schema validation for a JSONPath query, then pass an object `{ path: JSON
|
|
|
242
220
|
```ts
|
|
243
221
|
import * as z from "zod";
|
|
244
222
|
|
|
245
|
-
|
|
223
|
+
const stream = new ReadableStream({
|
|
246
224
|
start(controller) {
|
|
247
225
|
controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
|
|
248
226
|
controller.close();
|
|
@@ -253,29 +231,26 @@ await new ReadableStream({
|
|
|
253
231
|
{ path: "$.foo[*]", schema: z.number() },
|
|
254
232
|
{ path: "$.bar[*]", schema: z.string() },
|
|
255
233
|
]),
|
|
256
|
-
)
|
|
257
|
-
.pipeTo(
|
|
258
|
-
new WritableStream({
|
|
259
|
-
write(record) {
|
|
260
|
-
if (record.key === "$.foo[*]") {
|
|
261
|
-
// Type of record.value is number
|
|
262
|
-
} else {
|
|
263
|
-
// Type of record.value is string
|
|
264
|
-
}
|
|
265
|
-
},
|
|
266
|
-
}),
|
|
267
234
|
);
|
|
235
|
+
|
|
236
|
+
for await (const record of stream) {
|
|
237
|
+
if (record.key === "$.foo[*]") {
|
|
238
|
+
// Type of record.value is `number`
|
|
239
|
+
} else {
|
|
240
|
+
// Type of record.value is `string`
|
|
241
|
+
}
|
|
242
|
+
}
|
|
268
243
|
```
|
|
269
244
|
|
|
270
245
|
For JSONPath queries with no schema, emitted values will have the `unknown` type.
|
|
271
246
|
|
|
272
|
-
The type of the `key` property will be either the string literal `path` from the input paramter (such as `"$.foo[*]`) or whatever you put in the `key` property of the input. For example:
|
|
247
|
+
The type of the `key` property will be either the string literal `path` from the input paramter (such as `"$.foo[*]`) or whatever you put in the `key` property of the input, so you can use it to discriminate between object types in the output. For example:
|
|
273
248
|
|
|
274
249
|
<!-- prettier-ignore -->
|
|
275
250
|
```ts
|
|
276
251
|
import * as z from "zod";
|
|
277
252
|
|
|
278
|
-
|
|
253
|
+
const stream = new ReadableStream({
|
|
279
254
|
start(controller) {
|
|
280
255
|
controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
|
|
281
256
|
controller.close();
|
|
@@ -286,18 +261,15 @@ await new ReadableStream({
|
|
|
286
261
|
{ key: "foo", path: "$.foo[*]", schema: z.number() },
|
|
287
262
|
{ key: "bar", path: "$.bar[*]", schema: z.string() },
|
|
288
263
|
]),
|
|
289
|
-
)
|
|
290
|
-
.pipeTo(
|
|
291
|
-
new WritableStream({
|
|
292
|
-
write(record) {
|
|
293
|
-
if (record.key === "foo") {
|
|
294
|
-
// Type of record.value is number
|
|
295
|
-
} else {
|
|
296
|
-
// Type of record.value is string
|
|
297
|
-
}
|
|
298
|
-
},
|
|
299
|
-
}),
|
|
300
264
|
);
|
|
265
|
+
|
|
266
|
+
for await (const record of stream) {
|
|
267
|
+
if (record.key === "foo") {
|
|
268
|
+
// Type of record.value is `number`
|
|
269
|
+
} else {
|
|
270
|
+
// Type of record.value is `string`
|
|
271
|
+
}
|
|
272
|
+
}
|
|
301
273
|
```
|
|
302
274
|
|
|
303
275
|
> [!TIP]
|
|
@@ -309,7 +281,7 @@ json-web-streams supports a subset of JSONPath. Currently the supported componen
|
|
|
309
281
|
|
|
310
282
|
- **The root node**, represented by the symbol `$` which must be the first character of any JSONPath query.
|
|
311
283
|
|
|
312
|
-
- **Name selectors** which are like accessing a property in a JS object. For instance if you have an object like `{ "foo": { bar: 5 } }`, then `$.foo.bar` refers to the value `5`. You can also write this in the more verbose bracket notation like `$["foo"]["bar"]
|
|
284
|
+
- **Name selectors** which are like accessing a property in a JS object. For instance if you have an object like `{ "foo": { bar: 5 } }`, then `$.foo.bar` refers to the value `5`. You can also write this in the more verbose bracket notation like `$["foo"]["bar"]`, which is useful if your key names include characters that need escaping. You can also mix them like `$.foo["bar"]` or use single quotes like `$['foo']['bar']` - all of these JSONPath queries have the same meaning.
|
|
313
285
|
|
|
314
286
|
- **Wildcard selectors** which select every value in an array or object. With this JSON `{ "foo": { "a": 1, "b": 2, "c": 3 } }`, the JSONPath query `$.foo[*]` would emit the three individual numbers `1`, `2`, and `3`. If the inner object was changed to an array like `{ "foo": [1, 2, 3] }`, the same JSONPath query would emit the same values.
|
|
315
287
|
|
|
@@ -327,7 +299,7 @@ Or if the array is at the root if the object like this data:
|
|
|
327
299
|
[{ "x": 1 }, { "x": 2 }, { "x": 3 }]
|
|
328
300
|
```
|
|
329
301
|
|
|
330
|
-
then you'd write something like `$[*]` to emit each object (`{ x: 1}`, `{x: 2}`, `{x: 3}`), or `$[*].
|
|
302
|
+
then you'd write something like `$[*]` to emit each object (`{ x: 1}`, `{x: 2}`, `{x: 3}`), or `$[*].x` to emit just the numbers (`1`, `2`, `3`).
|
|
331
303
|
|
|
332
304
|
To emit the whole object at once (okay in that case you wouldn't use this library, but maybe just for testing, or for `multi` mode) you just use `$`.
|
|
333
305
|
|
|
@@ -352,7 +324,7 @@ You want to get all the values in `foo` and all the values in `bar`. You could d
|
|
|
352
324
|
|
|
353
325
|
<!-- prettier-ignore -->
|
|
354
326
|
```ts
|
|
355
|
-
|
|
327
|
+
const stream = new ReadableStream({
|
|
356
328
|
start(controller) {
|
|
357
329
|
controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
|
|
358
330
|
controller.close();
|
|
@@ -360,25 +332,22 @@ await new ReadableStream({
|
|
|
360
332
|
})
|
|
361
333
|
.pipeThrough(
|
|
362
334
|
new JSONParseStream(["$.foo[*]", "$.bar[*]"]),
|
|
363
|
-
)
|
|
364
|
-
.pipeTo(
|
|
365
|
-
new WritableStream({
|
|
366
|
-
write(record) {
|
|
367
|
-
if (record.key === "$.foo[*]") {
|
|
368
|
-
// 1, 2
|
|
369
|
-
} else {
|
|
370
|
-
// a, b, c
|
|
371
|
-
}
|
|
372
|
-
},
|
|
373
|
-
}),
|
|
374
335
|
);
|
|
336
|
+
|
|
337
|
+
for await (const record of stream) {
|
|
338
|
+
if (record.key === "$.foo[*]") {
|
|
339
|
+
// 1, 2
|
|
340
|
+
} else {
|
|
341
|
+
// a, b, c
|
|
342
|
+
}
|
|
343
|
+
}
|
|
375
344
|
```
|
|
376
345
|
|
|
377
346
|
Or you could use one JSONPath query with a wildcard, and then use `.wildcardKeys` to distinguish the objects:
|
|
378
347
|
|
|
379
348
|
<!-- prettier-ignore -->
|
|
380
349
|
```ts
|
|
381
|
-
|
|
350
|
+
const stream = new ReadableStream({
|
|
382
351
|
start(controller) {
|
|
383
352
|
controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
|
|
384
353
|
controller.close();
|
|
@@ -386,18 +355,15 @@ await new ReadableStream({
|
|
|
386
355
|
})
|
|
387
356
|
.pipeThrough(
|
|
388
357
|
new JSONParseStream(["$[*][*]"]),
|
|
389
|
-
)
|
|
390
|
-
.pipeTo(
|
|
391
|
-
new WritableStream({
|
|
392
|
-
write(record) {
|
|
393
|
-
if (record.wildcardKeys[0] === "foo") {
|
|
394
|
-
// 1, 2
|
|
395
|
-
} else {
|
|
396
|
-
// a, b, c
|
|
397
|
-
}
|
|
398
|
-
},
|
|
399
|
-
}),
|
|
400
358
|
);
|
|
359
|
+
|
|
360
|
+
for await (const record of stream) {
|
|
361
|
+
if (record.wildcardKeys[0] === "foo") {
|
|
362
|
+
// 1, 2
|
|
363
|
+
} else {
|
|
364
|
+
// a, b, c
|
|
365
|
+
}
|
|
366
|
+
}
|
|
401
367
|
```
|
|
402
368
|
|
|
403
369
|
Using multiple JSONPath queries is a little more explicit, but using wildcard keys is more concise, especially if you had more than just two types of objects. And instead of known keys like `foo` and `bar` your JSON had some unknown keys, then using a wildcard would be your only option.
|
|
@@ -410,7 +376,7 @@ In this example, Zod schemas enforce that `record.value` is either a `string` or
|
|
|
410
376
|
```ts
|
|
411
377
|
import * as z from "zod";
|
|
412
378
|
|
|
413
|
-
|
|
379
|
+
const stream = new ReadableStream({
|
|
414
380
|
start(controller) {
|
|
415
381
|
controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
|
|
416
382
|
controller.close();
|
|
@@ -421,16 +387,13 @@ await new ReadableStream({
|
|
|
421
387
|
{ path: "$.foo[*]", schema: z.number() },
|
|
422
388
|
{ path: "$.bar[*]", schema: z.string() },
|
|
423
389
|
]),
|
|
424
|
-
)
|
|
425
|
-
.pipeTo(
|
|
426
|
-
new WritableStream({
|
|
427
|
-
write(record) {
|
|
428
|
-
if (record.key === "$.foo[*]") {
|
|
429
|
-
// 1, 2
|
|
430
|
-
} else {
|
|
431
|
-
// a, b, c
|
|
432
|
-
}
|
|
433
|
-
},
|
|
434
|
-
}),
|
|
435
390
|
);
|
|
391
|
+
|
|
392
|
+
for await (const record of stream) {
|
|
393
|
+
if (record.key === "$.foo[*]") {
|
|
394
|
+
// Type of record.value is `number` rather than `unknown`
|
|
395
|
+
} else {
|
|
396
|
+
// Type of record.value is `string` rather than `unknown`
|
|
397
|
+
}
|
|
398
|
+
}
|
|
436
399
|
```
|
package/dist/JSONParseStream.js
CHANGED
|
@@ -3,19 +3,13 @@ import { jsonPathToPathArray } from "./jsonPathToPathArray.js";
|
|
|
3
3
|
|
|
4
4
|
//#region src/JSONParseStream.ts
|
|
5
5
|
const isEqual = (x, y) => {
|
|
6
|
-
if (
|
|
7
|
-
|
|
8
|
-
if (y.mode === "ARRAY") return true;
|
|
9
|
-
else if (y.mode === "OBJECT") return true;
|
|
10
|
-
} else if (y.mode === "OBJECT" && x.value === y.key) return true;
|
|
11
|
-
return false;
|
|
6
|
+
if (x.type === "wildcard") return y.mode === "ARRAY" || y.mode === "OBJECT";
|
|
7
|
+
return y.mode === "OBJECT" && x.value === y.key;
|
|
12
8
|
};
|
|
13
9
|
var JSONParseStream = class extends TransformStream {
|
|
14
10
|
_parser;
|
|
15
11
|
constructor(jsonPaths, options) {
|
|
16
12
|
let parser;
|
|
17
|
-
let minPathArrayLength = Infinity;
|
|
18
|
-
let maxPathArrayLength = -Infinity;
|
|
19
13
|
const jsonPathInfos = jsonPaths.map((row) => {
|
|
20
14
|
let key;
|
|
21
15
|
let path;
|
|
@@ -32,66 +26,42 @@ var JSONParseStream = class extends TransformStream {
|
|
|
32
26
|
if (wildcardIndexes === void 0) wildcardIndexes = [];
|
|
33
27
|
wildcardIndexes.push(i);
|
|
34
28
|
}
|
|
35
|
-
const matches = pathArray.length === 0 ? "yes" : "unknown";
|
|
36
|
-
if (pathArray.length > maxPathArrayLength) maxPathArrayLength = pathArray.length;
|
|
37
|
-
if (pathArray.length < minPathArrayLength) minPathArrayLength = pathArray.length;
|
|
38
29
|
return {
|
|
39
30
|
key,
|
|
40
|
-
matches,
|
|
41
31
|
path,
|
|
42
32
|
pathArray,
|
|
43
33
|
validate: schema?.["~standard"].validate,
|
|
44
34
|
wildcardIndexes
|
|
45
35
|
};
|
|
46
36
|
});
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (!isEqual(pathArray[j], stackComponent)) {
|
|
63
|
-
if (j < pathArray.length - 1) pathMatches = "noBeforeEnd";
|
|
64
|
-
else pathMatches = "noAtEnd";
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
info.matches = pathMatches;
|
|
69
|
-
if (pathMatches === "yes") jsonPathInfosThatMatch.add(info);
|
|
70
|
-
else jsonPathInfosThatMatch.delete(info);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
37
|
+
const makeLevel = (jsonPathInfos$1, depth, matchesAbove) => {
|
|
38
|
+
return {
|
|
39
|
+
jsonPathInfos: jsonPathInfos$1,
|
|
40
|
+
matchesHere: jsonPathInfos$1.some((info) => info.pathArray.length === depth),
|
|
41
|
+
matchesAbove,
|
|
42
|
+
matchesBelow: jsonPathInfos$1.some((info) => info.pathArray.length > depth)
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
const levels = [makeLevel(jsonPathInfos, 0, false)];
|
|
46
|
+
const updateLevel = (depth) => {
|
|
47
|
+
const parent = levels[depth - 1];
|
|
48
|
+
levels[depth] = makeLevel(parent.jsonPathInfos.filter((info) => info.pathArray.length >= depth && isEqual(info.pathArray[depth - 1], parser)), depth, parent.matchesAbove || parent.matchesHere);
|
|
73
49
|
};
|
|
74
50
|
super({
|
|
75
51
|
start(controller) {
|
|
76
52
|
parser = new JSONParseStreamRaw({
|
|
77
53
|
multi: options?.multi,
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
onPop: (
|
|
82
|
-
|
|
83
|
-
for (const info of jsonPathInfos) if (info.matches !== "unknown" && stackLength < info.pathArray.length) {
|
|
84
|
-
info.matches = "unknown";
|
|
85
|
-
jsonPathInfosThatMatch.delete(info);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
onPush: (stackLength) => {
|
|
90
|
-
if (stackLength <= maxPathArrayLength && stackLength >= minPathArrayLength) updateMatches("push");
|
|
54
|
+
parseWholeValue: () => !levels[parser.stack.length].matchesBelow,
|
|
55
|
+
onKey: updateLevel,
|
|
56
|
+
onPush: updateLevel,
|
|
57
|
+
onPop: (depth) => {
|
|
58
|
+
levels.length = depth + 1;
|
|
91
59
|
},
|
|
92
60
|
onValue: (value) => {
|
|
93
|
-
|
|
94
|
-
|
|
61
|
+
const depth = parser.stack.length;
|
|
62
|
+
const level = levels[depth];
|
|
63
|
+
if (level.matchesHere) for (const { key, path, pathArray, validate, wildcardIndexes } of level.jsonPathInfos) {
|
|
64
|
+
if (pathArray.length !== depth) continue;
|
|
95
65
|
let valueToEmit;
|
|
96
66
|
if (validate) {
|
|
97
67
|
const result = validate(value);
|
|
@@ -116,8 +86,8 @@ var JSONParseStream = class extends TransformStream {
|
|
|
116
86
|
key: key ?? path,
|
|
117
87
|
value: valueToEmit
|
|
118
88
|
});
|
|
119
|
-
}
|
|
120
|
-
if (
|
|
89
|
+
}
|
|
90
|
+
if (level.matchesAbove) return;
|
|
121
91
|
const type = typeof value;
|
|
122
92
|
if (!(type === "string" || type === "number" || type === "boolean" || value === null)) {
|
|
123
93
|
for (const row of parser.stack) row.value = void 0;
|
|
@@ -129,9 +99,8 @@ var JSONParseStream = class extends TransformStream {
|
|
|
129
99
|
transform(chunk) {
|
|
130
100
|
parser.write(chunk);
|
|
131
101
|
},
|
|
132
|
-
flush(
|
|
102
|
+
flush() {
|
|
133
103
|
parser.checkEnd();
|
|
134
|
-
controller.terminate();
|
|
135
104
|
}
|
|
136
105
|
});
|
|
137
106
|
this._parser = parser;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//#region src/JSONParseStreamRaw.d.ts
|
|
2
2
|
type Token = "LEFT_BRACE" | "RIGHT_BRACE" | "LEFT_BRACKET" | "RIGHT_BRACKET" | "COLON" | "COMMA" | "TRUE" | "FALSE" | "NULL" | "STRING" | "NUMBER";
|
|
3
3
|
type ParserState = "VALUE" | "KEY" | "VALUE_AFTER_COMMA" | "KEY_AFTER_COMMA";
|
|
4
|
-
type TokenizerState = "START" | "TRUE1" | "TRUE2" | "TRUE3" | "FALSE1" | "FALSE2" | "FALSE3" | "FALSE4" | "NULL1" | "NULL2" | "NULL3" | "NUMBER-" | "NUMBER0" | "NUMBER" | "STRING1" | "STRING2" | "STRING3" | "STRING4" | "STRING5" | "STRING6";
|
|
4
|
+
type TokenizerState = "START" | "TRUE1" | "TRUE2" | "TRUE3" | "FALSE1" | "FALSE2" | "FALSE3" | "FALSE4" | "NULL1" | "NULL2" | "NULL3" | "NUMBER-" | "NUMBER0" | "NUMBER" | "NUMBER_DOT" | "NUMBER_FRACTION" | "NUMBER_E" | "NUMBER_E_SIGN" | "NUMBER_EXPONENT" | "STRING1" | "STRING2" | "STRING3" | "STRING4" | "STRING5" | "STRING6";
|
|
5
5
|
type Mode = "OBJECT" | "ARRAY";
|
|
6
6
|
type Key = string | number;
|
|
7
7
|
type Value = any;
|
|
@@ -26,32 +26,45 @@ declare class JSONParseStreamRaw {
|
|
|
26
26
|
onPush: OnPopPush | undefined;
|
|
27
27
|
onValue: OnValue;
|
|
28
28
|
unicode: string | undefined;
|
|
29
|
-
highSurrogate: number | undefined;
|
|
30
29
|
seenRootObject: boolean;
|
|
31
30
|
multi: boolean | undefined;
|
|
32
31
|
multiIndex: number;
|
|
32
|
+
parseWholeValue: (() => boolean) | undefined;
|
|
33
|
+
captureDisabled: boolean;
|
|
34
|
+
capturePieces: string[] | undefined;
|
|
35
|
+
captureStart: number;
|
|
36
|
+
captureDepth: number;
|
|
37
|
+
captureInString: boolean;
|
|
38
|
+
captureEscaped: boolean;
|
|
33
39
|
constructor({
|
|
34
40
|
multi,
|
|
35
41
|
onKey,
|
|
36
42
|
onPop,
|
|
37
43
|
onPush,
|
|
38
|
-
onValue
|
|
44
|
+
onValue,
|
|
45
|
+
parseWholeValue
|
|
39
46
|
}: {
|
|
40
47
|
multi?: boolean;
|
|
41
48
|
onKey?: OnPopPush;
|
|
42
49
|
onPop?: OnPopPush;
|
|
43
50
|
onPush?: OnPopPush;
|
|
44
51
|
onValue: OnValue;
|
|
52
|
+
parseWholeValue?: () => boolean;
|
|
45
53
|
});
|
|
46
54
|
charError(char: string, i: number): void;
|
|
47
55
|
parseError(token: Token, value: Value, i: number): void;
|
|
56
|
+
shouldCapture(): boolean;
|
|
57
|
+
startCapture(text: string, i: number): number;
|
|
58
|
+
scanCapture(text: string, start: number): number;
|
|
59
|
+
endCapture(json: string): void;
|
|
60
|
+
parseCaptureStrict(json: string): void;
|
|
48
61
|
write(text: string): void;
|
|
49
|
-
push(): void;
|
|
62
|
+
push(mode: Mode, value: object): void;
|
|
50
63
|
pop(): void;
|
|
51
|
-
setKey(key:
|
|
64
|
+
setKey(key: string): void;
|
|
52
65
|
emit(value: Value): void;
|
|
53
66
|
onToken(token: Token, value: Value, i: number): void;
|
|
54
|
-
|
|
67
|
+
endNumber(i: number): void;
|
|
55
68
|
checkEnd(): void;
|
|
56
69
|
}
|
|
57
70
|
//#endregion
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
//#region src/JSONParseStreamRaw.ts
|
|
2
|
-
const
|
|
3
|
-
" ",
|
|
4
|
-
" ",
|
|
5
|
-
"\n",
|
|
6
|
-
"\r"
|
|
7
|
-
]);
|
|
2
|
+
const isWhitespace = (n) => n === " " || n === "\n" || n === "\r" || n === " ";
|
|
8
3
|
var JSONParseStreamRaw = class {
|
|
9
4
|
tokenizerState = "START";
|
|
10
5
|
state = "VALUE";
|
|
@@ -19,16 +14,23 @@ var JSONParseStreamRaw = class {
|
|
|
19
14
|
onPush;
|
|
20
15
|
onValue;
|
|
21
16
|
unicode;
|
|
22
|
-
highSurrogate;
|
|
23
17
|
seenRootObject = false;
|
|
24
18
|
multi;
|
|
25
19
|
multiIndex = 0;
|
|
26
|
-
|
|
20
|
+
parseWholeValue;
|
|
21
|
+
captureDisabled = false;
|
|
22
|
+
capturePieces;
|
|
23
|
+
captureStart = 0;
|
|
24
|
+
captureDepth = 0;
|
|
25
|
+
captureInString = false;
|
|
26
|
+
captureEscaped = false;
|
|
27
|
+
constructor({ multi, onKey, onPop, onPush, onValue, parseWholeValue }) {
|
|
27
28
|
this.multi = multi;
|
|
28
29
|
this.onKey = onKey;
|
|
29
30
|
this.onPop = onPop;
|
|
30
31
|
this.onPush = onPush;
|
|
31
32
|
this.onValue = onValue;
|
|
33
|
+
this.parseWholeValue = parseWholeValue;
|
|
32
34
|
}
|
|
33
35
|
charError(char, i) {
|
|
34
36
|
throw new Error(`Unexpected ${JSON.stringify(char)} at position ${this.position + i} in state ${this.tokenizerState}`);
|
|
@@ -36,13 +38,91 @@ var JSONParseStreamRaw = class {
|
|
|
36
38
|
parseError(token, value, i) {
|
|
37
39
|
throw new Error(`Unexpected ${token}${value ? `(${JSON.stringify(value)})` : ""} at position ${this.position + i} in state ${this.state}`);
|
|
38
40
|
}
|
|
41
|
+
shouldCapture() {
|
|
42
|
+
return this.parseWholeValue !== void 0 && !this.captureDisabled && (this.state === "VALUE" || this.state === "VALUE_AFTER_COMMA") && this.parseWholeValue();
|
|
43
|
+
}
|
|
44
|
+
startCapture(text, i) {
|
|
45
|
+
this.captureStart = this.position + i;
|
|
46
|
+
this.captureDepth = 0;
|
|
47
|
+
this.captureInString = false;
|
|
48
|
+
this.captureEscaped = false;
|
|
49
|
+
const end = this.scanCapture(text, i);
|
|
50
|
+
if (end === -1) {
|
|
51
|
+
this.capturePieces = [text.slice(i)];
|
|
52
|
+
return text.length - 1;
|
|
53
|
+
}
|
|
54
|
+
this.endCapture(text.slice(i, end + 1));
|
|
55
|
+
return end;
|
|
56
|
+
}
|
|
57
|
+
scanCapture(text, start) {
|
|
58
|
+
const l = text.length;
|
|
59
|
+
let j = start;
|
|
60
|
+
if (this.captureEscaped && j < l) {
|
|
61
|
+
this.captureEscaped = false;
|
|
62
|
+
j += 1;
|
|
63
|
+
}
|
|
64
|
+
let depth = this.captureDepth;
|
|
65
|
+
let inString = this.captureInString;
|
|
66
|
+
for (; j < l; j++) {
|
|
67
|
+
const code = text.charCodeAt(j);
|
|
68
|
+
if (inString) {
|
|
69
|
+
if (code === 92) {
|
|
70
|
+
j += 1;
|
|
71
|
+
if (j === l) this.captureEscaped = true;
|
|
72
|
+
} else if (code === 34) inString = false;
|
|
73
|
+
} else if (code === 34) inString = true;
|
|
74
|
+
else if (code === 123 || code === 91) depth += 1;
|
|
75
|
+
else if (code === 125 || code === 93) {
|
|
76
|
+
depth -= 1;
|
|
77
|
+
if (depth === 0) return j;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
this.captureDepth = depth;
|
|
81
|
+
this.captureInString = inString;
|
|
82
|
+
return -1;
|
|
83
|
+
}
|
|
84
|
+
endCapture(json) {
|
|
85
|
+
this.capturePieces = void 0;
|
|
86
|
+
let value;
|
|
87
|
+
try {
|
|
88
|
+
value = JSON.parse(json);
|
|
89
|
+
} catch {
|
|
90
|
+
this.parseCaptureStrict(json);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (this.stack.length === 0) this.seenRootObject = true;
|
|
94
|
+
if (this.value) this.value[this.key] = value;
|
|
95
|
+
this.emit(value);
|
|
96
|
+
}
|
|
97
|
+
parseCaptureStrict(json) {
|
|
98
|
+
const position = this.position;
|
|
99
|
+
this.position = this.captureStart;
|
|
100
|
+
this.captureDisabled = true;
|
|
101
|
+
this.write(json);
|
|
102
|
+
this.captureDisabled = false;
|
|
103
|
+
this.position = position;
|
|
104
|
+
}
|
|
39
105
|
write(text) {
|
|
40
|
-
|
|
106
|
+
let i = 0;
|
|
107
|
+
if (this.capturePieces !== void 0) {
|
|
108
|
+
const end = this.scanCapture(text, 0);
|
|
109
|
+
if (end === -1) {
|
|
110
|
+
this.capturePieces.push(text);
|
|
111
|
+
this.position += text.length;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
this.capturePieces.push(text.slice(0, end + 1));
|
|
115
|
+
this.endCapture(this.capturePieces.join(""));
|
|
116
|
+
i = end + 1;
|
|
117
|
+
}
|
|
118
|
+
for (const l = text.length; i < l; i++) {
|
|
41
119
|
const n = text[i];
|
|
42
|
-
if (!this.multi && this.stack.length === 0 && this.seenRootObject && !
|
|
43
|
-
if (this.tokenizerState === "START") if (n === "{") this.
|
|
120
|
+
if (!this.multi && this.stack.length === 0 && this.seenRootObject && !isWhitespace(n)) return this.charError(n, i);
|
|
121
|
+
if (this.tokenizerState === "START") if (n === "{") if (this.shouldCapture()) i = this.startCapture(text, i);
|
|
122
|
+
else this.onToken("LEFT_BRACE", "{", i);
|
|
44
123
|
else if (n === "}") this.onToken("RIGHT_BRACE", "}", i);
|
|
45
|
-
else if (n === "[") this.
|
|
124
|
+
else if (n === "[") if (this.shouldCapture()) i = this.startCapture(text, i);
|
|
125
|
+
else this.onToken("LEFT_BRACKET", "[", i);
|
|
46
126
|
else if (n === "]") this.onToken("RIGHT_BRACKET", "]", i);
|
|
47
127
|
else if (n === ":") this.onToken("COLON", ":", i);
|
|
48
128
|
else if (n === ",") this.onToken("COMMA", ",", i);
|
|
@@ -61,15 +141,21 @@ var JSONParseStreamRaw = class {
|
|
|
61
141
|
} else if (n >= "1" && n <= "9") {
|
|
62
142
|
this.string = n;
|
|
63
143
|
this.tokenizerState = "NUMBER";
|
|
64
|
-
} else if (
|
|
144
|
+
} else if (isWhitespace(n)) {} else if (n === "␞" && this.multi && this.stack.length === 0) {} else return this.charError(n, i);
|
|
65
145
|
else if (this.tokenizerState === "STRING1") if (n === "\"") {
|
|
66
146
|
this.tokenizerState = "START";
|
|
67
147
|
this.onToken("STRING", this.string, i);
|
|
68
148
|
this.string = void 0;
|
|
69
149
|
} else if (n === "\\") this.tokenizerState = "STRING2";
|
|
70
150
|
else {
|
|
71
|
-
|
|
72
|
-
|
|
151
|
+
let j = i;
|
|
152
|
+
for (; j < l; j++) {
|
|
153
|
+
const code = text.charCodeAt(j);
|
|
154
|
+
if (code === 34 || code === 92) break;
|
|
155
|
+
if (code <= 31) return this.charError(text[j], j);
|
|
156
|
+
}
|
|
157
|
+
this.string += text.slice(i, j);
|
|
158
|
+
i = j - 1;
|
|
73
159
|
}
|
|
74
160
|
else if (this.tokenizerState === "STRING2") if (n === "\"") {
|
|
75
161
|
this.string += "\"";
|
|
@@ -100,59 +186,75 @@ var JSONParseStreamRaw = class {
|
|
|
100
186
|
this.tokenizerState = "STRING3";
|
|
101
187
|
} else return this.charError(n, i);
|
|
102
188
|
else if (this.tokenizerState === "STRING3" || this.tokenizerState === "STRING4" || this.tokenizerState === "STRING5" || this.tokenizerState === "STRING6") {
|
|
189
|
+
if (!(n >= "0" && n <= "9" || n >= "a" && n <= "f" || n >= "A" && n <= "F")) return this.charError(n, i);
|
|
103
190
|
this.unicode += n;
|
|
104
191
|
if (this.tokenizerState === "STRING3") this.tokenizerState = "STRING4";
|
|
105
192
|
else if (this.tokenizerState === "STRING4") this.tokenizerState = "STRING5";
|
|
106
193
|
else if (this.tokenizerState === "STRING5") this.tokenizerState = "STRING6";
|
|
107
194
|
else if (this.tokenizerState === "STRING6") {
|
|
108
195
|
const intVal = Number.parseInt(this.unicode, 16);
|
|
109
|
-
if (Number.isNaN(intVal)) return this.charError(n, i);
|
|
110
196
|
this.unicode = void 0;
|
|
111
|
-
|
|
112
|
-
this.string += String.fromCharCode(this.highSurrogate, intVal);
|
|
113
|
-
this.highSurrogate = void 0;
|
|
114
|
-
} else if (this.highSurrogate === void 0 && intVal >= 55296 && intVal < 56320) this.highSurrogate = intVal;
|
|
115
|
-
else {
|
|
116
|
-
if (this.highSurrogate !== void 0) {
|
|
117
|
-
this.string += String.fromCharCode(this.highSurrogate);
|
|
118
|
-
this.highSurrogate = void 0;
|
|
119
|
-
}
|
|
120
|
-
this.string += String.fromCharCode(intVal);
|
|
121
|
-
}
|
|
197
|
+
this.string += String.fromCharCode(intVal);
|
|
122
198
|
this.tokenizerState = "STRING1";
|
|
123
199
|
}
|
|
124
|
-
} else if (this.tokenizerState === "NUMBER"
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
200
|
+
} else if (this.tokenizerState === "NUMBER") if (n >= "0" && n <= "9") this.string += n;
|
|
201
|
+
else if (n === ".") {
|
|
202
|
+
this.string += n;
|
|
203
|
+
this.tokenizerState = "NUMBER_DOT";
|
|
204
|
+
} else if (n === "e" || n === "E") {
|
|
205
|
+
this.string += n;
|
|
206
|
+
this.tokenizerState = "NUMBER_E";
|
|
207
|
+
} else {
|
|
208
|
+
this.endNumber(i);
|
|
209
|
+
i--;
|
|
210
|
+
}
|
|
211
|
+
else if (this.tokenizerState === "NUMBER_FRACTION") if (n >= "0" && n <= "9") this.string += n;
|
|
212
|
+
else if (n === "e" || n === "E") {
|
|
213
|
+
this.string += n;
|
|
214
|
+
this.tokenizerState = "NUMBER_E";
|
|
215
|
+
} else {
|
|
216
|
+
this.endNumber(i);
|
|
217
|
+
i--;
|
|
218
|
+
}
|
|
219
|
+
else if (this.tokenizerState === "NUMBER0") if (n >= "0" && n <= "9") return this.charError("0", i - 1);
|
|
220
|
+
else if (n === ".") {
|
|
221
|
+
this.string += n;
|
|
222
|
+
this.tokenizerState = "NUMBER_DOT";
|
|
223
|
+
} else if (n === "e" || n === "E") {
|
|
224
|
+
this.string += n;
|
|
225
|
+
this.tokenizerState = "NUMBER_E";
|
|
226
|
+
} else {
|
|
227
|
+
this.endNumber(i);
|
|
228
|
+
i--;
|
|
229
|
+
}
|
|
230
|
+
else if (this.tokenizerState === "NUMBER-") if (n === "0") {
|
|
231
|
+
this.string += n;
|
|
232
|
+
this.tokenizerState = "NUMBER0";
|
|
233
|
+
} else if (n >= "1" && n <= "9") {
|
|
234
|
+
this.string += n;
|
|
235
|
+
this.tokenizerState = "NUMBER";
|
|
236
|
+
} else return this.charError(n, i);
|
|
237
|
+
else if (this.tokenizerState === "NUMBER_DOT") if (n >= "0" && n <= "9") {
|
|
238
|
+
this.string += n;
|
|
239
|
+
this.tokenizerState = "NUMBER_FRACTION";
|
|
240
|
+
} else return this.charError(n, i);
|
|
241
|
+
else if (this.tokenizerState === "NUMBER_E") if (n >= "0" && n <= "9") {
|
|
242
|
+
this.string += n;
|
|
243
|
+
this.tokenizerState = "NUMBER_EXPONENT";
|
|
244
|
+
} else if (n === "+" || n === "-") {
|
|
245
|
+
this.string += n;
|
|
246
|
+
this.tokenizerState = "NUMBER_E_SIGN";
|
|
247
|
+
} else return this.charError(n, i);
|
|
248
|
+
else if (this.tokenizerState === "NUMBER_E_SIGN") if (n >= "0" && n <= "9") {
|
|
249
|
+
this.string += n;
|
|
250
|
+
this.tokenizerState = "NUMBER_EXPONENT";
|
|
251
|
+
} else return this.charError(n, i);
|
|
252
|
+
else if (this.tokenizerState === "NUMBER_EXPONENT") if (n >= "0" && n <= "9") this.string += n;
|
|
253
|
+
else {
|
|
254
|
+
this.endNumber(i);
|
|
255
|
+
i--;
|
|
256
|
+
}
|
|
257
|
+
else if (this.tokenizerState === "TRUE1") if (n === "r") this.tokenizerState = "TRUE2";
|
|
156
258
|
else return this.charError(n, i);
|
|
157
259
|
else if (this.tokenizerState === "TRUE2") if (n === "u") this.tokenizerState = "TRUE3";
|
|
158
260
|
else return this.charError(n, i);
|
|
@@ -181,12 +283,16 @@ var JSONParseStreamRaw = class {
|
|
|
181
283
|
}
|
|
182
284
|
this.position += text.length;
|
|
183
285
|
}
|
|
184
|
-
push() {
|
|
286
|
+
push(mode, value) {
|
|
185
287
|
this.stack.push({
|
|
186
288
|
value: this.value,
|
|
187
289
|
key: this.key,
|
|
188
290
|
mode: this.mode
|
|
189
291
|
});
|
|
292
|
+
if (this.value) this.value[this.key] = value;
|
|
293
|
+
this.value = value;
|
|
294
|
+
this.mode = mode;
|
|
295
|
+
this.key = mode === "ARRAY" ? 0 : void 0;
|
|
190
296
|
this.onPush?.(this.stack.length);
|
|
191
297
|
}
|
|
192
298
|
pop() {
|
|
@@ -201,7 +307,7 @@ var JSONParseStreamRaw = class {
|
|
|
201
307
|
}
|
|
202
308
|
setKey(key) {
|
|
203
309
|
this.key = key;
|
|
204
|
-
|
|
310
|
+
this.onKey?.(this.stack.length);
|
|
205
311
|
}
|
|
206
312
|
emit(value) {
|
|
207
313
|
if (this.mode) this.state = "COMMA";
|
|
@@ -216,18 +322,10 @@ var JSONParseStreamRaw = class {
|
|
|
216
322
|
if (this.value) this.value[this.key] = value;
|
|
217
323
|
this.emit(value);
|
|
218
324
|
} else if (token === "LEFT_BRACE") {
|
|
219
|
-
this.push();
|
|
220
|
-
if (this.value) this.value = this.value[this.key] = {};
|
|
221
|
-
else this.value = {};
|
|
222
|
-
this.setKey(void 0);
|
|
325
|
+
this.push("OBJECT", {});
|
|
223
326
|
this.state = "KEY";
|
|
224
|
-
this.mode = "OBJECT";
|
|
225
327
|
} else if (token === "LEFT_BRACKET") {
|
|
226
|
-
this.push();
|
|
227
|
-
if (this.value) this.value = this.value[this.key] = [];
|
|
228
|
-
else this.value = [];
|
|
229
|
-
this.setKey(0);
|
|
230
|
-
this.mode = "ARRAY";
|
|
328
|
+
this.push("ARRAY", []);
|
|
231
329
|
this.state = "VALUE";
|
|
232
330
|
} else if (token === "RIGHT_BRACE") if (this.mode === "OBJECT" && this.state !== "VALUE_AFTER_COMMA") this.pop();
|
|
233
331
|
else return this.parseError(token, value, i);
|
|
@@ -250,17 +348,22 @@ var JSONParseStreamRaw = class {
|
|
|
250
348
|
else return this.parseError(token, value, i);
|
|
251
349
|
else return this.parseError(token, value, i);
|
|
252
350
|
}
|
|
253
|
-
|
|
254
|
-
const number =
|
|
255
|
-
|
|
351
|
+
endNumber(i) {
|
|
352
|
+
const number = Number(this.string);
|
|
353
|
+
this.string = void 0;
|
|
354
|
+
this.tokenizerState = "START";
|
|
256
355
|
this.onToken("NUMBER", number, i);
|
|
257
356
|
}
|
|
258
357
|
checkEnd() {
|
|
358
|
+
if (this.capturePieces !== void 0) {
|
|
359
|
+
const json = this.capturePieces.join("");
|
|
360
|
+
this.capturePieces = void 0;
|
|
361
|
+
this.parseCaptureStrict(json);
|
|
362
|
+
}
|
|
259
363
|
if (this.stack.length > 0) throw new Error(`Unexpected end of input at position ${this.position} in state ${this.state}`);
|
|
260
|
-
if (this.
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
} else if (!this.seenRootObject) throw new Error("No data in input");
|
|
364
|
+
if (this.tokenizerState === "NUMBER" || this.tokenizerState === "NUMBER0" || this.tokenizerState === "NUMBER_FRACTION" || this.tokenizerState === "NUMBER_EXPONENT") this.endNumber(this.position - 1);
|
|
365
|
+
if (this.tokenizerState !== "START") throw new Error(`Unexpected end of input at position ${this.position} in state ${this.tokenizerState}`);
|
|
366
|
+
if (!this.seenRootObject) throw new Error("No data in input");
|
|
264
367
|
}
|
|
265
368
|
};
|
|
266
369
|
|
|
@@ -8,22 +8,23 @@ const jsonPathToPathArray = (path) => {
|
|
|
8
8
|
} catch (error) {
|
|
9
9
|
throw new Error(`Error parsing JSONPath "${path}"`, { cause: error });
|
|
10
10
|
}
|
|
11
|
-
return parsed.segments.
|
|
11
|
+
return parsed.segments.map((segment) => {
|
|
12
12
|
if (segment.type === "ChildSegment") {
|
|
13
13
|
const node = segment.node;
|
|
14
14
|
if (node.type === "MemberNameShorthand") return {
|
|
15
15
|
type: "key",
|
|
16
16
|
value: node.value
|
|
17
17
|
};
|
|
18
|
-
else if (node.type === "BracketedSelection")
|
|
18
|
+
else if (node.type === "BracketedSelection") {
|
|
19
|
+
if (node.selectors.length !== 1) throw new Error(`Multiple selectors in brackets are not supported in JSONPath "${path}"`);
|
|
20
|
+
const selector = node.selectors[0];
|
|
19
21
|
if (selector.type === "NameSelector") return {
|
|
20
22
|
type: "key",
|
|
21
23
|
value: selector.value
|
|
22
24
|
};
|
|
23
25
|
else if (selector.type === "WildcardSelector") return { type: "wildcard" };
|
|
24
26
|
else throw new Error(`Unsupported node: ${JSON.stringify(node)}`);
|
|
25
|
-
});
|
|
26
|
-
else if (node.type === "WildcardSelector") return { type: "wildcard" };
|
|
27
|
+
} else if (node.type === "WildcardSelector") return { type: "wildcard" };
|
|
27
28
|
else throw new Error(`${segment.type} node type not supported`);
|
|
28
29
|
} else throw new Error(`${segment.type} segment type not supported`);
|
|
29
30
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "json-web-streams",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Streaming JSON parser built on top of the Web Streams API, so it works in web browsers, Node.js, and many other environments",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web streams",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"license": "Apache-2.0",
|
|
16
16
|
"author": "Jeremy Scheff <jeremy@zengm.com> (https://zengm.com/)",
|
|
17
|
+
"sideEffects": false,
|
|
17
18
|
"type": "module",
|
|
18
19
|
"main": "dist/index.js",
|
|
19
20
|
"types": "dist/index.d.ts",
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
"dist"
|
|
22
23
|
],
|
|
23
24
|
"scripts": {
|
|
24
|
-
"bench": "vitest bench",
|
|
25
|
+
"bench": "vitest bench --run",
|
|
25
26
|
"build": "tsdown --unbundle",
|
|
26
27
|
"format": "prettier --write .",
|
|
27
28
|
"lint": "npm-run-all --parallel lint:oxlint lint:tsc",
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
"lint:tsc": "tsc",
|
|
30
31
|
"prepack": "node --run build",
|
|
31
32
|
"prepare": "husky",
|
|
32
|
-
"test": "vitest"
|
|
33
|
+
"test": "vitest --run"
|
|
33
34
|
},
|
|
34
35
|
"lint-staged": {
|
|
35
36
|
"*.{js,cjs,mjs,jsx,json,scss,ts,cts,mts,tsx,md}": "prettier --write"
|
|
@@ -42,19 +43,19 @@
|
|
|
42
43
|
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
|
|
43
44
|
"@types/node": "^24.9.1",
|
|
44
45
|
"husky": "^9.1.7",
|
|
45
|
-
"lint-staged": "^16.2.
|
|
46
|
+
"lint-staged": "^16.2.6",
|
|
46
47
|
"npm-run-all2": "^8.0.4",
|
|
47
|
-
"oxlint": "^1.
|
|
48
|
-
"oxlint-tsgolint": "^0.
|
|
48
|
+
"oxlint": "^1.24.0",
|
|
49
|
+
"oxlint-tsgolint": "^0.3.0",
|
|
49
50
|
"prettier": "^3.6.2",
|
|
50
51
|
"prettier-plugin-packagejson": "^2.5.19",
|
|
51
52
|
"tsdown": "^0.15.9",
|
|
52
53
|
"typescript": "^5.9.3",
|
|
53
|
-
"vitest": "^
|
|
54
|
+
"vitest": "^4.0.3",
|
|
54
55
|
"zod": "^4.1.12"
|
|
55
56
|
},
|
|
56
57
|
"engines": {
|
|
57
58
|
"node": ">=22",
|
|
58
|
-
"pnpm": "^
|
|
59
|
+
"pnpm": "^11.0.0"
|
|
59
60
|
}
|
|
60
61
|
}
|