json-web-streams 1.0.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 +132 -106
- package/dist/JSONParseStream.d.ts +12 -7
- package/dist/JSONParseStream.js +58 -63
- package/dist/JSONParseStreamRaw.d.ts +31 -7
- package/dist/JSONParseStreamRaw.js +194 -79
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/jsonPathToPathArray.js +5 -4
- package/package.json +11 -9
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,28 +50,16 @@ 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
|
|
70
56
|
const jsonParseStream = new JSONParseStream(
|
|
71
|
-
jsonPaths: (JSONPath | { path: JSONPath; schema
|
|
57
|
+
jsonPaths: (JSONPath | { path: JSONPath; key?: Key; schema?: StandardSchemaV1 })[],
|
|
72
58
|
options?: { multi?: boolean },
|
|
73
59
|
);
|
|
74
60
|
```
|
|
75
61
|
|
|
76
|
-
### `jsonPaths: (JSONPath | { path: JSONPath; schema
|
|
62
|
+
### `jsonPaths: (JSONPath | { path: JSONPath; key?: Key; schema?: StandardSchemaV1 })[]`
|
|
77
63
|
|
|
78
64
|
The first argument to `JSONParseStream` is an array specifying what objects to emit from the stream. The `JSONPath` type is a string containing a JSONPath query. JSONPath is a query language for JSON to let you pick out specific values from a JSON object.
|
|
79
65
|
|
|
@@ -102,7 +88,15 @@ but you can have as many as you want:
|
|
|
102
88
|
>
|
|
103
89
|
> In total this query means "emit every value in the array/object in the `foo` property of the overall JSON object". So for this JSON `{ foo: ["A", "B", "C"] }` it would emit the three values `"A"`, `"B"`, and `"C"`.
|
|
104
90
|
|
|
105
|
-
The values of the `jsonPaths` array can either be `JSONPath` strings, or objects like `{ path: JSONPath; schema
|
|
91
|
+
The values of the `jsonPaths` array can either be `JSONPath` strings, or objects like `{ path: JSONPath; key?: Key; schema?: StandardSchemaV1 }` to specify the additional optional `key` and `schema` properties.
|
|
92
|
+
|
|
93
|
+
#### `key?: Key`
|
|
94
|
+
|
|
95
|
+
If `key` is defined, it will be propagated through to the `key` property on output objects. So it can be any type, but typically is probably something like a number, string, or Symbol that you want to use to discriminate between types of outputs when you have multiple entries in the `jsonPaths` array. When `key` is not defined, then the value of `path` is used instead. See the [`JSONParseStream` output](#jsonparsestream-output) section below for examples.
|
|
96
|
+
|
|
97
|
+
#### `schema?: StandardSchemaV1`
|
|
98
|
+
|
|
99
|
+
`schema` is a schema validator from any library supporting the [Standard Schema specification](https://github.com/standard-schema/standard-schema) such as Zod, Valibot, or ArkType. When you supply a schema like this, each value will be validated before it is emitted by the stream, and emitted values will have correct TypeScript types rather than being `unknown`. For more details, see the [Schema validation and types for `JSONParseStream` output](#schema-validation-and-types-for-jsonparsestream-output) section below.
|
|
106
100
|
|
|
107
101
|
### `options?: { multi?: boolean }`
|
|
108
102
|
|
|
@@ -137,60 +131,76 @@ Output from `JSONParseStream` has this format:
|
|
|
137
131
|
```ts
|
|
138
132
|
type JSONParseStreamOutput<T = unknown> = {
|
|
139
133
|
value: T;
|
|
140
|
-
|
|
134
|
+
key: Key;
|
|
141
135
|
wildcardKeys?: string[];
|
|
142
136
|
};
|
|
143
137
|
```
|
|
144
138
|
|
|
145
139
|
`value` is the value selected by one of your JSONPath queries.
|
|
146
140
|
|
|
147
|
-
`
|
|
141
|
+
`key` is the JSONPath query (from the `jsonPaths` parameter of `JSONParseStream`) that matched `value`, or the value from the `key` property inside `jsonPaths` if that was supplied.
|
|
148
142
|
|
|
149
|
-
If you only have one JSONPath query, you can ignore `
|
|
143
|
+
If you only have one JSONPath query, you can ignore `key`. But if you have more than one, `key` may be helpful when processing stream output to distinguish between different types of values. For example:
|
|
150
144
|
|
|
151
145
|
<!-- prettier-ignore -->
|
|
152
146
|
```ts
|
|
153
|
-
|
|
147
|
+
const stream = new ReadableStream({
|
|
154
148
|
start(controller) {
|
|
155
149
|
controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
|
|
156
150
|
controller.close();
|
|
157
151
|
},
|
|
158
152
|
})
|
|
159
|
-
.pipeThrough(new JSONParseStream(["$.bar[*]", "$.foo[*]"]))
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Or with a manually defined key:
|
|
165
|
+
|
|
166
|
+
<!-- prettier-ignore -->
|
|
167
|
+
```ts
|
|
168
|
+
const stream = new ReadableStream({
|
|
169
|
+
start(controller) {
|
|
170
|
+
controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
|
|
171
|
+
controller.close();
|
|
172
|
+
},
|
|
173
|
+
})
|
|
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
|
+
}
|
|
171
183
|
```
|
|
172
184
|
|
|
173
185
|
`wildcardKeys` is defined when you have a wildcard in an object (not an array) somewhere in your JSONPath. For example:
|
|
174
186
|
|
|
175
187
|
<!-- prettier-ignore -->
|
|
176
188
|
```ts
|
|
177
|
-
|
|
189
|
+
const stream = new ReadableStream({
|
|
178
190
|
start(controller) {
|
|
179
191
|
controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
|
|
180
192
|
controller.close();
|
|
181
193
|
},
|
|
182
194
|
})
|
|
183
|
-
.pipeThrough(new JSONParseStream(["$[*]"]))
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}),
|
|
190
|
-
);
|
|
195
|
+
.pipeThrough(new JSONParseStream(["$[*]"]));
|
|
196
|
+
|
|
197
|
+
for await (const record of stream) {
|
|
198
|
+
console.log(record);
|
|
199
|
+
}
|
|
200
|
+
|
|
191
201
|
// Output:
|
|
192
|
-
// {
|
|
193
|
-
// {
|
|
202
|
+
// { key: "$[*]", value: [1, 2], wildcardKeys: ["foo"] },
|
|
203
|
+
// { key: "$[*]", value: ["a", "b", "c"], wildcardKeys: ["bar"] },
|
|
194
204
|
```
|
|
195
205
|
|
|
196
206
|
The purpose of `wildcardKeys` is to allow you to easily distinguish different types of objects. `wildcardKeys` has one entry for each wildcard object in your JSONPath query.
|
|
@@ -198,7 +208,7 @@ The purpose of `wildcardKeys` is to allow you to easily distinguish different ty
|
|
|
198
208
|
> [!WARNING]
|
|
199
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.
|
|
200
210
|
>
|
|
201
|
-
> 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.
|
|
202
212
|
|
|
203
213
|
#### Schema validation and types for `JSONParseStream` output
|
|
204
214
|
|
|
@@ -210,7 +220,7 @@ To use schema validation for a JSONPath query, then pass an object `{ path: JSON
|
|
|
210
220
|
```ts
|
|
211
221
|
import * as z from "zod";
|
|
212
222
|
|
|
213
|
-
|
|
223
|
+
const stream = new ReadableStream({
|
|
214
224
|
start(controller) {
|
|
215
225
|
controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
|
|
216
226
|
controller.close();
|
|
@@ -221,32 +231,57 @@ await new ReadableStream({
|
|
|
221
231
|
{ path: "$.foo[*]", schema: z.number() },
|
|
222
232
|
{ path: "$.bar[*]", schema: z.string() },
|
|
223
233
|
]),
|
|
224
|
-
)
|
|
225
|
-
.pipeTo(
|
|
226
|
-
new WritableStream({
|
|
227
|
-
write(record) {
|
|
228
|
-
if (record.path === "$.foo[*]") {
|
|
229
|
-
// Type of record.value is number
|
|
230
|
-
} else {
|
|
231
|
-
// Type of record.value is string
|
|
232
|
-
}
|
|
233
|
-
},
|
|
234
|
-
}),
|
|
235
234
|
);
|
|
236
|
-
```
|
|
237
235
|
|
|
238
|
-
|
|
239
|
-
|
|
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
|
+
}
|
|
243
|
+
```
|
|
240
244
|
|
|
241
245
|
For JSONPath queries with no schema, emitted values will have the `unknown` type.
|
|
242
246
|
|
|
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:
|
|
248
|
+
|
|
249
|
+
<!-- prettier-ignore -->
|
|
250
|
+
```ts
|
|
251
|
+
import * as z from "zod";
|
|
252
|
+
|
|
253
|
+
const stream = new ReadableStream({
|
|
254
|
+
start(controller) {
|
|
255
|
+
controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
|
|
256
|
+
controller.close();
|
|
257
|
+
},
|
|
258
|
+
})
|
|
259
|
+
.pipeThrough(
|
|
260
|
+
new JSONParseStream([
|
|
261
|
+
{ key: "foo", path: "$.foo[*]", schema: z.number() },
|
|
262
|
+
{ key: "bar", path: "$.bar[*]", schema: z.string() },
|
|
263
|
+
]),
|
|
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
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
> [!TIP]
|
|
276
|
+
> If you only want to validate values or override keys for some queries, you can mix `{ path: JSONPath; key?: Key; schema?: StandardSchemaV1 }` and `JSONPath` in the `jsonPaths` array.
|
|
277
|
+
|
|
243
278
|
## JSONPath
|
|
244
279
|
|
|
245
280
|
json-web-streams supports a subset of JSONPath. Currently the supported components are:
|
|
246
281
|
|
|
247
282
|
- **The root node**, represented by the symbol `$` which must be the first character of any JSONPath query.
|
|
248
283
|
|
|
249
|
-
- **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.
|
|
250
285
|
|
|
251
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.
|
|
252
287
|
|
|
@@ -264,7 +299,7 @@ Or if the array is at the root if the object like this data:
|
|
|
264
299
|
[{ "x": 1 }, { "x": 2 }, { "x": 3 }]
|
|
265
300
|
```
|
|
266
301
|
|
|
267
|
-
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`).
|
|
268
303
|
|
|
269
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 `$`.
|
|
270
305
|
|
|
@@ -285,11 +320,11 @@ Let's say you have this JSON:
|
|
|
285
320
|
{ "foo": [1, 2], "bar": ["a", "b", "c"] }
|
|
286
321
|
```
|
|
287
322
|
|
|
288
|
-
You want to get all the values in `foo` and all the values in `bar`. You could define them as two separate JSONPath queries and then distinguish the output with `.
|
|
323
|
+
You want to get all the values in `foo` and all the values in `bar`. You could define them as two separate JSONPath queries and then distinguish the output with `.key`:
|
|
289
324
|
|
|
290
325
|
<!-- prettier-ignore -->
|
|
291
326
|
```ts
|
|
292
|
-
|
|
327
|
+
const stream = new ReadableStream({
|
|
293
328
|
start(controller) {
|
|
294
329
|
controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
|
|
295
330
|
controller.close();
|
|
@@ -297,25 +332,22 @@ await new ReadableStream({
|
|
|
297
332
|
})
|
|
298
333
|
.pipeThrough(
|
|
299
334
|
new JSONParseStream(["$.foo[*]", "$.bar[*]"]),
|
|
300
|
-
)
|
|
301
|
-
.pipeTo(
|
|
302
|
-
new WritableStream({
|
|
303
|
-
write(record) {
|
|
304
|
-
if (record.path === "$.foo[*]") {
|
|
305
|
-
// 1, 2
|
|
306
|
-
} else {
|
|
307
|
-
// a, b, c
|
|
308
|
-
}
|
|
309
|
-
},
|
|
310
|
-
}),
|
|
311
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
|
+
}
|
|
312
344
|
```
|
|
313
345
|
|
|
314
346
|
Or you could use one JSONPath query with a wildcard, and then use `.wildcardKeys` to distinguish the objects:
|
|
315
347
|
|
|
316
348
|
<!-- prettier-ignore -->
|
|
317
349
|
```ts
|
|
318
|
-
|
|
350
|
+
const stream = new ReadableStream({
|
|
319
351
|
start(controller) {
|
|
320
352
|
controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
|
|
321
353
|
controller.close();
|
|
@@ -323,18 +355,15 @@ await new ReadableStream({
|
|
|
323
355
|
})
|
|
324
356
|
.pipeThrough(
|
|
325
357
|
new JSONParseStream(["$[*][*]"]),
|
|
326
|
-
)
|
|
327
|
-
.pipeTo(
|
|
328
|
-
new WritableStream({
|
|
329
|
-
write(record) {
|
|
330
|
-
if (record.wildcardKeys[0] === "foo") {
|
|
331
|
-
// 1, 2
|
|
332
|
-
} else {
|
|
333
|
-
// a, b, c
|
|
334
|
-
}
|
|
335
|
-
},
|
|
336
|
-
}),
|
|
337
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
|
+
}
|
|
338
367
|
```
|
|
339
368
|
|
|
340
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.
|
|
@@ -347,7 +376,7 @@ In this example, Zod schemas enforce that `record.value` is either a `string` or
|
|
|
347
376
|
```ts
|
|
348
377
|
import * as z from "zod";
|
|
349
378
|
|
|
350
|
-
|
|
379
|
+
const stream = new ReadableStream({
|
|
351
380
|
start(controller) {
|
|
352
381
|
controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
|
|
353
382
|
controller.close();
|
|
@@ -358,16 +387,13 @@ await new ReadableStream({
|
|
|
358
387
|
{ path: "$.foo[*]", schema: z.number() },
|
|
359
388
|
{ path: "$.bar[*]", schema: z.string() },
|
|
360
389
|
]),
|
|
361
|
-
)
|
|
362
|
-
.pipeTo(
|
|
363
|
-
new WritableStream({
|
|
364
|
-
write(record) {
|
|
365
|
-
if (record.path === "$.foo[*]") {
|
|
366
|
-
// 1, 2
|
|
367
|
-
} else {
|
|
368
|
-
// a, b, c
|
|
369
|
-
}
|
|
370
|
-
},
|
|
371
|
-
}),
|
|
372
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
|
+
}
|
|
373
399
|
```
|
|
@@ -4,20 +4,25 @@ import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
|
4
4
|
|
|
5
5
|
//#region src/JSONParseStream.d.ts
|
|
6
6
|
type JSONParseStreamOutput<T> = T extends {
|
|
7
|
+
key?: infer K | undefined;
|
|
7
8
|
path: infer P extends JSONPath;
|
|
8
|
-
schema
|
|
9
|
+
schema?: infer S extends StandardSchemaV1 | undefined;
|
|
9
10
|
} ? {
|
|
10
|
-
|
|
11
|
-
value: StandardSchemaV1.InferOutput<S>;
|
|
11
|
+
value: S extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<S> : unknown;
|
|
12
12
|
wildcardKeys?: string[];
|
|
13
|
-
}
|
|
14
|
-
|
|
13
|
+
} & (undefined extends K ? {
|
|
14
|
+
key: P;
|
|
15
|
+
} : {
|
|
16
|
+
key: K;
|
|
17
|
+
}) : T extends JSONPath ? {
|
|
18
|
+
key: T;
|
|
15
19
|
value: unknown;
|
|
16
20
|
wildcardKeys?: string[];
|
|
17
21
|
} : never;
|
|
18
|
-
declare class JSONParseStream<T extends readonly (JSONPath | {
|
|
22
|
+
declare class JSONParseStream<const Key extends unknown, T extends readonly (JSONPath | {
|
|
23
|
+
key?: Key;
|
|
19
24
|
path: JSONPath;
|
|
20
|
-
schema
|
|
25
|
+
schema?: StandardSchemaV1;
|
|
21
26
|
})[]> extends TransformStream<string, JSONParseStreamOutput<T[number]>> {
|
|
22
27
|
_parser: JSONParseStreamRaw;
|
|
23
28
|
constructor(jsonPaths: T, options?: {
|
package/dist/JSONParseStream.js
CHANGED
|
@@ -2,33 +2,21 @@ import { JSONParseStreamRaw } from "./JSONParseStreamRaw.js";
|
|
|
2
2
|
import { jsonPathToPathArray } from "./jsonPathToPathArray.js";
|
|
3
3
|
|
|
4
4
|
//#region src/JSONParseStream.ts
|
|
5
|
-
const stackToPathComponent = (stackComponent) => {
|
|
6
|
-
if (stackComponent.mode === "OBJECT" && stackComponent.key !== void 0) return {
|
|
7
|
-
type: "key",
|
|
8
|
-
value: stackComponent.key
|
|
9
|
-
};
|
|
10
|
-
if (stackComponent.mode === "ARRAY") return { type: "wildcard" };
|
|
11
|
-
throw new Error(`Unexpected mode "${stackComponent.mode}"`);
|
|
12
|
-
};
|
|
13
5
|
const isEqual = (x, y) => {
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
if (x.type === "wildcard") return true;
|
|
17
|
-
if (x.value === y.value) return true;
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
return x.type === "wildcard";
|
|
6
|
+
if (x.type === "wildcard") return y.mode === "ARRAY" || y.mode === "OBJECT";
|
|
7
|
+
return y.mode === "OBJECT" && x.value === y.key;
|
|
21
8
|
};
|
|
22
9
|
var JSONParseStream = class extends TransformStream {
|
|
23
10
|
_parser;
|
|
24
11
|
constructor(jsonPaths, options) {
|
|
25
12
|
let parser;
|
|
26
|
-
const multi = options?.multi ?? false;
|
|
27
13
|
const jsonPathInfos = jsonPaths.map((row) => {
|
|
14
|
+
let key;
|
|
28
15
|
let path;
|
|
29
16
|
let schema;
|
|
30
17
|
if (typeof row === "string") path = row;
|
|
31
18
|
else {
|
|
19
|
+
key = row.key;
|
|
32
20
|
path = row.path;
|
|
33
21
|
schema = row.schema;
|
|
34
22
|
}
|
|
@@ -39,63 +27,71 @@ var JSONParseStream = class extends TransformStream {
|
|
|
39
27
|
wildcardIndexes.push(i);
|
|
40
28
|
}
|
|
41
29
|
return {
|
|
30
|
+
key,
|
|
42
31
|
path,
|
|
43
32
|
pathArray,
|
|
44
|
-
schema,
|
|
33
|
+
validate: schema?.["~standard"].validate,
|
|
45
34
|
wildcardIndexes
|
|
46
35
|
};
|
|
47
36
|
});
|
|
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);
|
|
49
|
+
};
|
|
48
50
|
super({
|
|
49
51
|
start(controller) {
|
|
50
52
|
parser = new JSONParseStreamRaw({
|
|
51
|
-
multi,
|
|
53
|
+
multi: options?.multi,
|
|
54
|
+
parseWholeValue: () => !levels[parser.stack.length].matchesBelow,
|
|
55
|
+
onKey: updateLevel,
|
|
56
|
+
onPush: updateLevel,
|
|
57
|
+
onPop: (depth) => {
|
|
58
|
+
levels.length = depth + 1;
|
|
59
|
+
},
|
|
52
60
|
onValue: (value) => {
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (result.issues) throw new Error(JSON.stringify(result.issues, null, 2));
|
|
71
|
-
valueToEmit = result.value;
|
|
72
|
-
} else valueToEmit = value;
|
|
73
|
-
let wildcardKeys;
|
|
74
|
-
if (wildcardIndexes) for (const index of wildcardIndexes) {
|
|
75
|
-
const pathComponent = stackPathArray[index];
|
|
76
|
-
if (pathComponent?.type === "key") {
|
|
77
|
-
if (!wildcardKeys) wildcardKeys = [];
|
|
78
|
-
wildcardKeys.push(pathComponent.value);
|
|
79
|
-
}
|
|
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;
|
|
65
|
+
let valueToEmit;
|
|
66
|
+
if (validate) {
|
|
67
|
+
const result = validate(value);
|
|
68
|
+
if (result instanceof Promise) throw new TypeError("Schema validation must be synchronous");
|
|
69
|
+
if (result.issues) throw new Error(JSON.stringify(result.issues, null, 2));
|
|
70
|
+
valueToEmit = result.value;
|
|
71
|
+
} else valueToEmit = value;
|
|
72
|
+
let wildcardKeys;
|
|
73
|
+
if (wildcardIndexes) for (const index of wildcardIndexes) {
|
|
74
|
+
const stackComponent = parser.stack[index + 1] ?? parser;
|
|
75
|
+
if (stackComponent.mode === "OBJECT" && stackComponent.key !== void 0) {
|
|
76
|
+
if (!wildcardKeys) wildcardKeys = [];
|
|
77
|
+
wildcardKeys.push(stackComponent.key);
|
|
80
78
|
}
|
|
81
|
-
if (wildcardKeys) controller.enqueue({
|
|
82
|
-
path,
|
|
83
|
-
value: valueToEmit,
|
|
84
|
-
wildcardKeys
|
|
85
|
-
});
|
|
86
|
-
else controller.enqueue({
|
|
87
|
-
path,
|
|
88
|
-
value: valueToEmit
|
|
89
|
-
});
|
|
90
|
-
} else keep = true;
|
|
91
|
-
else {
|
|
92
|
-
const type = typeof value;
|
|
93
|
-
if (type === "string" || type === "number" || type === "boolean" || value === null) keep = true;
|
|
94
79
|
}
|
|
80
|
+
if (wildcardKeys) controller.enqueue({
|
|
81
|
+
key: key ?? path,
|
|
82
|
+
value: valueToEmit,
|
|
83
|
+
wildcardKeys
|
|
84
|
+
});
|
|
85
|
+
else controller.enqueue({
|
|
86
|
+
key: key ?? path,
|
|
87
|
+
value: valueToEmit
|
|
88
|
+
});
|
|
95
89
|
}
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
if (level.matchesAbove) return;
|
|
91
|
+
const type = typeof value;
|
|
92
|
+
if (!(type === "string" || type === "number" || type === "boolean" || value === null)) {
|
|
93
|
+
for (const row of parser.stack) row.value = void 0;
|
|
94
|
+
if (typeof parser.value === "object" && parser.value !== null && parser.key !== void 0) parser.value[parser.key] = void 0;
|
|
99
95
|
}
|
|
100
96
|
}
|
|
101
97
|
});
|
|
@@ -103,9 +99,8 @@ var JSONParseStream = class extends TransformStream {
|
|
|
103
99
|
transform(chunk) {
|
|
104
100
|
parser.write(chunk);
|
|
105
101
|
},
|
|
106
|
-
flush(
|
|
102
|
+
flush() {
|
|
107
103
|
parser.checkEnd();
|
|
108
|
-
controller.terminate();
|
|
109
104
|
}
|
|
110
105
|
});
|
|
111
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;
|
|
@@ -10,6 +10,7 @@ type Stack = {
|
|
|
10
10
|
value: Value | undefined;
|
|
11
11
|
mode: Mode | undefined;
|
|
12
12
|
}[];
|
|
13
|
+
type OnPopPush = (stackLength: number) => void;
|
|
13
14
|
type OnValue = (value: Value) => void;
|
|
14
15
|
declare class JSONParseStreamRaw {
|
|
15
16
|
tokenizerState: TokenizerState;
|
|
@@ -20,27 +21,50 @@ declare class JSONParseStreamRaw {
|
|
|
20
21
|
key: Key | undefined;
|
|
21
22
|
value: Value;
|
|
22
23
|
position: number;
|
|
24
|
+
onKey: OnPopPush | undefined;
|
|
25
|
+
onPop: OnPopPush | undefined;
|
|
26
|
+
onPush: OnPopPush | undefined;
|
|
23
27
|
onValue: OnValue;
|
|
24
28
|
unicode: string | undefined;
|
|
25
|
-
highSurrogate: number | undefined;
|
|
26
29
|
seenRootObject: boolean;
|
|
27
|
-
multi: boolean;
|
|
30
|
+
multi: boolean | undefined;
|
|
28
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;
|
|
29
39
|
constructor({
|
|
30
40
|
multi,
|
|
31
|
-
|
|
41
|
+
onKey,
|
|
42
|
+
onPop,
|
|
43
|
+
onPush,
|
|
44
|
+
onValue,
|
|
45
|
+
parseWholeValue
|
|
32
46
|
}: {
|
|
33
|
-
multi
|
|
47
|
+
multi?: boolean;
|
|
48
|
+
onKey?: OnPopPush;
|
|
49
|
+
onPop?: OnPopPush;
|
|
50
|
+
onPush?: OnPopPush;
|
|
34
51
|
onValue: OnValue;
|
|
52
|
+
parseWholeValue?: () => boolean;
|
|
35
53
|
});
|
|
36
54
|
charError(char: string, i: number): void;
|
|
37
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;
|
|
38
61
|
write(text: string): void;
|
|
39
|
-
push(): void;
|
|
62
|
+
push(mode: Mode, value: object): void;
|
|
40
63
|
pop(): void;
|
|
64
|
+
setKey(key: string): void;
|
|
41
65
|
emit(value: Value): void;
|
|
42
66
|
onToken(token: Token, value: Value, i: number): void;
|
|
43
|
-
|
|
67
|
+
endNumber(i: number): void;
|
|
44
68
|
checkEnd(): void;
|
|
45
69
|
}
|
|
46
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";
|
|
@@ -14,15 +9,28 @@ var JSONParseStreamRaw = class {
|
|
|
14
9
|
key;
|
|
15
10
|
value;
|
|
16
11
|
position = 0;
|
|
12
|
+
onKey;
|
|
13
|
+
onPop;
|
|
14
|
+
onPush;
|
|
17
15
|
onValue;
|
|
18
16
|
unicode;
|
|
19
|
-
highSurrogate;
|
|
20
17
|
seenRootObject = false;
|
|
21
18
|
multi;
|
|
22
19
|
multiIndex = 0;
|
|
23
|
-
|
|
24
|
-
|
|
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 }) {
|
|
25
28
|
this.multi = multi;
|
|
29
|
+
this.onKey = onKey;
|
|
30
|
+
this.onPop = onPop;
|
|
31
|
+
this.onPush = onPush;
|
|
32
|
+
this.onValue = onValue;
|
|
33
|
+
this.parseWholeValue = parseWholeValue;
|
|
26
34
|
}
|
|
27
35
|
charError(char, i) {
|
|
28
36
|
throw new Error(`Unexpected ${JSON.stringify(char)} at position ${this.position + i} in state ${this.tokenizerState}`);
|
|
@@ -30,13 +38,91 @@ var JSONParseStreamRaw = class {
|
|
|
30
38
|
parseError(token, value, i) {
|
|
31
39
|
throw new Error(`Unexpected ${token}${value ? `(${JSON.stringify(value)})` : ""} at position ${this.position + i} in state ${this.state}`);
|
|
32
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
|
+
}
|
|
33
105
|
write(text) {
|
|
34
|
-
|
|
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++) {
|
|
35
119
|
const n = text[i];
|
|
36
|
-
if (!this.multi && this.stack.length === 0 && this.seenRootObject && !
|
|
37
|
-
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);
|
|
38
123
|
else if (n === "}") this.onToken("RIGHT_BRACE", "}", i);
|
|
39
|
-
else if (n === "[") this.
|
|
124
|
+
else if (n === "[") if (this.shouldCapture()) i = this.startCapture(text, i);
|
|
125
|
+
else this.onToken("LEFT_BRACKET", "[", i);
|
|
40
126
|
else if (n === "]") this.onToken("RIGHT_BRACKET", "]", i);
|
|
41
127
|
else if (n === ":") this.onToken("COLON", ":", i);
|
|
42
128
|
else if (n === ",") this.onToken("COMMA", ",", i);
|
|
@@ -55,15 +141,21 @@ var JSONParseStreamRaw = class {
|
|
|
55
141
|
} else if (n >= "1" && n <= "9") {
|
|
56
142
|
this.string = n;
|
|
57
143
|
this.tokenizerState = "NUMBER";
|
|
58
|
-
} else if (
|
|
144
|
+
} else if (isWhitespace(n)) {} else if (n === "␞" && this.multi && this.stack.length === 0) {} else return this.charError(n, i);
|
|
59
145
|
else if (this.tokenizerState === "STRING1") if (n === "\"") {
|
|
60
146
|
this.tokenizerState = "START";
|
|
61
147
|
this.onToken("STRING", this.string, i);
|
|
62
148
|
this.string = void 0;
|
|
63
149
|
} else if (n === "\\") this.tokenizerState = "STRING2";
|
|
64
150
|
else {
|
|
65
|
-
|
|
66
|
-
|
|
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;
|
|
67
159
|
}
|
|
68
160
|
else if (this.tokenizerState === "STRING2") if (n === "\"") {
|
|
69
161
|
this.string += "\"";
|
|
@@ -94,59 +186,75 @@ var JSONParseStreamRaw = class {
|
|
|
94
186
|
this.tokenizerState = "STRING3";
|
|
95
187
|
} else return this.charError(n, i);
|
|
96
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);
|
|
97
190
|
this.unicode += n;
|
|
98
191
|
if (this.tokenizerState === "STRING3") this.tokenizerState = "STRING4";
|
|
99
192
|
else if (this.tokenizerState === "STRING4") this.tokenizerState = "STRING5";
|
|
100
193
|
else if (this.tokenizerState === "STRING5") this.tokenizerState = "STRING6";
|
|
101
194
|
else if (this.tokenizerState === "STRING6") {
|
|
102
195
|
const intVal = Number.parseInt(this.unicode, 16);
|
|
103
|
-
if (Number.isNaN(intVal)) return this.charError(n, i);
|
|
104
196
|
this.unicode = void 0;
|
|
105
|
-
|
|
106
|
-
this.string += String.fromCharCode(this.highSurrogate, intVal);
|
|
107
|
-
this.highSurrogate = void 0;
|
|
108
|
-
} else if (this.highSurrogate === void 0 && intVal >= 55296 && intVal < 56320) this.highSurrogate = intVal;
|
|
109
|
-
else {
|
|
110
|
-
if (this.highSurrogate !== void 0) {
|
|
111
|
-
this.string += String.fromCharCode(this.highSurrogate);
|
|
112
|
-
this.highSurrogate = void 0;
|
|
113
|
-
}
|
|
114
|
-
this.string += String.fromCharCode(intVal);
|
|
115
|
-
}
|
|
197
|
+
this.string += String.fromCharCode(intVal);
|
|
116
198
|
this.tokenizerState = "STRING1";
|
|
117
199
|
}
|
|
118
|
-
} else if (this.tokenizerState === "NUMBER"
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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";
|
|
150
258
|
else return this.charError(n, i);
|
|
151
259
|
else if (this.tokenizerState === "TRUE2") if (n === "u") this.tokenizerState = "TRUE3";
|
|
152
260
|
else return this.charError(n, i);
|
|
@@ -175,12 +283,17 @@ var JSONParseStreamRaw = class {
|
|
|
175
283
|
}
|
|
176
284
|
this.position += text.length;
|
|
177
285
|
}
|
|
178
|
-
push() {
|
|
286
|
+
push(mode, value) {
|
|
179
287
|
this.stack.push({
|
|
180
288
|
value: this.value,
|
|
181
289
|
key: this.key,
|
|
182
290
|
mode: this.mode
|
|
183
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;
|
|
296
|
+
this.onPush?.(this.stack.length);
|
|
184
297
|
}
|
|
185
298
|
pop() {
|
|
186
299
|
const value = this.value;
|
|
@@ -189,8 +302,13 @@ var JSONParseStreamRaw = class {
|
|
|
189
302
|
this.key = parent.key;
|
|
190
303
|
this.mode = parent.mode;
|
|
191
304
|
this.emit(value);
|
|
305
|
+
this.onPop?.(this.stack.length);
|
|
192
306
|
if (!this.mode) this.state = "VALUE";
|
|
193
307
|
}
|
|
308
|
+
setKey(key) {
|
|
309
|
+
this.key = key;
|
|
310
|
+
this.onKey?.(this.stack.length);
|
|
311
|
+
}
|
|
194
312
|
emit(value) {
|
|
195
313
|
if (this.mode) this.state = "COMMA";
|
|
196
314
|
if (value === void 0) return;
|
|
@@ -204,18 +322,10 @@ var JSONParseStreamRaw = class {
|
|
|
204
322
|
if (this.value) this.value[this.key] = value;
|
|
205
323
|
this.emit(value);
|
|
206
324
|
} else if (token === "LEFT_BRACE") {
|
|
207
|
-
this.push();
|
|
208
|
-
if (this.value) this.value = this.value[this.key] = {};
|
|
209
|
-
else this.value = {};
|
|
210
|
-
this.key = void 0;
|
|
325
|
+
this.push("OBJECT", {});
|
|
211
326
|
this.state = "KEY";
|
|
212
|
-
this.mode = "OBJECT";
|
|
213
327
|
} else if (token === "LEFT_BRACKET") {
|
|
214
|
-
this.push();
|
|
215
|
-
if (this.value) this.value = this.value[this.key] = [];
|
|
216
|
-
else this.value = [];
|
|
217
|
-
this.key = 0;
|
|
218
|
-
this.mode = "ARRAY";
|
|
328
|
+
this.push("ARRAY", []);
|
|
219
329
|
this.state = "VALUE";
|
|
220
330
|
} else if (token === "RIGHT_BRACE") if (this.mode === "OBJECT" && this.state !== "VALUE_AFTER_COMMA") this.pop();
|
|
221
331
|
else return this.parseError(token, value, i);
|
|
@@ -223,7 +333,7 @@ var JSONParseStreamRaw = class {
|
|
|
223
333
|
else return this.parseError(token, value, i);
|
|
224
334
|
else return this.parseError(token, value, i);
|
|
225
335
|
else if (this.state === "KEY" || this.state === "KEY_AFTER_COMMA") if (token === "STRING") {
|
|
226
|
-
this.
|
|
336
|
+
this.setKey(value);
|
|
227
337
|
this.state = "COLON";
|
|
228
338
|
} else if (token === "RIGHT_BRACE" && this.state !== "KEY_AFTER_COMMA") this.pop();
|
|
229
339
|
else return this.parseError(token, value, i);
|
|
@@ -238,17 +348,22 @@ var JSONParseStreamRaw = class {
|
|
|
238
348
|
else return this.parseError(token, value, i);
|
|
239
349
|
else return this.parseError(token, value, i);
|
|
240
350
|
}
|
|
241
|
-
|
|
242
|
-
const number =
|
|
243
|
-
|
|
351
|
+
endNumber(i) {
|
|
352
|
+
const number = Number(this.string);
|
|
353
|
+
this.string = void 0;
|
|
354
|
+
this.tokenizerState = "START";
|
|
244
355
|
this.onToken("NUMBER", number, i);
|
|
245
356
|
}
|
|
246
357
|
checkEnd() {
|
|
358
|
+
if (this.capturePieces !== void 0) {
|
|
359
|
+
const json = this.capturePieces.join("");
|
|
360
|
+
this.capturePieces = void 0;
|
|
361
|
+
this.parseCaptureStrict(json);
|
|
362
|
+
}
|
|
247
363
|
if (this.stack.length > 0) throw new Error(`Unexpected end of input at position ${this.position} in state ${this.state}`);
|
|
248
|
-
if (this.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
} 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");
|
|
252
367
|
}
|
|
253
368
|
};
|
|
254
369
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { JSONParseStreamRaw } from "./JSONParseStreamRaw.js";
|
|
2
1
|
import { JSONPath } from "./jsonPathToPathArray.js";
|
|
3
2
|
import { JSONParseStream } from "./JSONParseStream.js";
|
|
4
|
-
export { JSONParseStream,
|
|
3
|
+
export { JSONParseStream, type JSONPath };
|
package/dist/index.js
CHANGED
|
@@ -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,6 +22,7 @@
|
|
|
21
22
|
"dist"
|
|
22
23
|
],
|
|
23
24
|
"scripts": {
|
|
25
|
+
"bench": "vitest bench --run",
|
|
24
26
|
"build": "tsdown --unbundle",
|
|
25
27
|
"format": "prettier --write .",
|
|
26
28
|
"lint": "npm-run-all --parallel lint:oxlint lint:tsc",
|
|
@@ -28,7 +30,7 @@
|
|
|
28
30
|
"lint:tsc": "tsc",
|
|
29
31
|
"prepack": "node --run build",
|
|
30
32
|
"prepare": "husky",
|
|
31
|
-
"test": "vitest"
|
|
33
|
+
"test": "vitest --run"
|
|
32
34
|
},
|
|
33
35
|
"lint-staged": {
|
|
34
36
|
"*.{js,cjs,mjs,jsx,json,scss,ts,cts,mts,tsx,md}": "prettier --write"
|
|
@@ -39,21 +41,21 @@
|
|
|
39
41
|
},
|
|
40
42
|
"devDependencies": {
|
|
41
43
|
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
|
|
42
|
-
"@types/node": "^24.
|
|
44
|
+
"@types/node": "^24.9.1",
|
|
43
45
|
"husky": "^9.1.7",
|
|
44
|
-
"lint-staged": "^16.2.
|
|
46
|
+
"lint-staged": "^16.2.6",
|
|
45
47
|
"npm-run-all2": "^8.0.4",
|
|
46
|
-
"oxlint": "^1.
|
|
47
|
-
"oxlint-tsgolint": "^0.
|
|
48
|
+
"oxlint": "^1.24.0",
|
|
49
|
+
"oxlint-tsgolint": "^0.3.0",
|
|
48
50
|
"prettier": "^3.6.2",
|
|
49
51
|
"prettier-plugin-packagejson": "^2.5.19",
|
|
50
|
-
"tsdown": "^0.15.
|
|
52
|
+
"tsdown": "^0.15.9",
|
|
51
53
|
"typescript": "^5.9.3",
|
|
52
|
-
"vitest": "^
|
|
54
|
+
"vitest": "^4.0.3",
|
|
53
55
|
"zod": "^4.1.12"
|
|
54
56
|
},
|
|
55
57
|
"engines": {
|
|
56
58
|
"node": ">=22",
|
|
57
|
-
"pnpm": "^
|
|
59
|
+
"pnpm": "^11.0.0"
|
|
58
60
|
}
|
|
59
61
|
}
|