json-web-streams 1.0.0 → 1.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/README.md +79 -16
- package/dist/JSONParseStream.d.ts +12 -7
- package/dist/JSONParseStream.js +86 -60
- package/dist/JSONParseStreamRaw.d.ts +13 -2
- package/dist/JSONParseStreamRaw.js +17 -5
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -68,12 +68,12 @@ await response.body
|
|
|
68
68
|
|
|
69
69
|
```ts
|
|
70
70
|
const jsonParseStream = new JSONParseStream(
|
|
71
|
-
jsonPaths: (JSONPath | { path: JSONPath; schema
|
|
71
|
+
jsonPaths: (JSONPath | { path: JSONPath; key?: Key; schema?: StandardSchemaV1 })[],
|
|
72
72
|
options?: { multi?: boolean },
|
|
73
73
|
);
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
### `jsonPaths: (JSONPath | { path: JSONPath; schema
|
|
76
|
+
### `jsonPaths: (JSONPath | { path: JSONPath; key?: Key; schema?: StandardSchemaV1 })[]`
|
|
77
77
|
|
|
78
78
|
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
79
|
|
|
@@ -102,7 +102,15 @@ but you can have as many as you want:
|
|
|
102
102
|
>
|
|
103
103
|
> 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
104
|
|
|
105
|
-
The values of the `jsonPaths` array can either be `JSONPath` strings, or objects like `{ path: JSONPath; schema
|
|
105
|
+
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.
|
|
106
|
+
|
|
107
|
+
#### `key?: Key`
|
|
108
|
+
|
|
109
|
+
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.
|
|
110
|
+
|
|
111
|
+
#### `schema?: StandardSchemaV1`
|
|
112
|
+
|
|
113
|
+
`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
114
|
|
|
107
115
|
### `options?: { multi?: boolean }`
|
|
108
116
|
|
|
@@ -137,16 +145,16 @@ Output from `JSONParseStream` has this format:
|
|
|
137
145
|
```ts
|
|
138
146
|
type JSONParseStreamOutput<T = unknown> = {
|
|
139
147
|
value: T;
|
|
140
|
-
|
|
148
|
+
key: Key;
|
|
141
149
|
wildcardKeys?: string[];
|
|
142
150
|
};
|
|
143
151
|
```
|
|
144
152
|
|
|
145
153
|
`value` is the value selected by one of your JSONPath queries.
|
|
146
154
|
|
|
147
|
-
`
|
|
155
|
+
`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
156
|
|
|
149
|
-
If you only have one JSONPath query, you can ignore `
|
|
157
|
+
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
158
|
|
|
151
159
|
<!-- prettier-ignore -->
|
|
152
160
|
```ts
|
|
@@ -160,7 +168,31 @@ await new ReadableStream({
|
|
|
160
168
|
.pipeTo(
|
|
161
169
|
new WritableStream({
|
|
162
170
|
write(record) {
|
|
163
|
-
if (record.
|
|
171
|
+
if (record.key === "$.bar[*]") {
|
|
172
|
+
// Do something with the values from bar
|
|
173
|
+
} else {
|
|
174
|
+
// Do something with the values from foo
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
}),
|
|
178
|
+
);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Or with a manually defined key:
|
|
182
|
+
|
|
183
|
+
<!-- prettier-ignore -->
|
|
184
|
+
```ts
|
|
185
|
+
await new ReadableStream({
|
|
186
|
+
start(controller) {
|
|
187
|
+
controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
|
|
188
|
+
controller.close();
|
|
189
|
+
},
|
|
190
|
+
})
|
|
191
|
+
.pipeThrough(new JSONParseStream([{ key: "bar", path: "$.bar[*]" }, "$.foo[*]"]))
|
|
192
|
+
.pipeTo(
|
|
193
|
+
new WritableStream({
|
|
194
|
+
write(record) {
|
|
195
|
+
if (record.key === "bar") {
|
|
164
196
|
// Do something with the values from bar
|
|
165
197
|
} else {
|
|
166
198
|
// Do something with the values from foo
|
|
@@ -189,8 +221,8 @@ await new ReadableStream({
|
|
|
189
221
|
}),
|
|
190
222
|
);
|
|
191
223
|
// Output:
|
|
192
|
-
// {
|
|
193
|
-
// {
|
|
224
|
+
// { key: "$[*]", value: [1, 2], wildcardKeys: ["foo"] },
|
|
225
|
+
// { key: "$[*]", value: ["a", "b", "c"], wildcardKeys: ["bar"] },
|
|
194
226
|
```
|
|
195
227
|
|
|
196
228
|
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.
|
|
@@ -225,7 +257,7 @@ await new ReadableStream({
|
|
|
225
257
|
.pipeTo(
|
|
226
258
|
new WritableStream({
|
|
227
259
|
write(record) {
|
|
228
|
-
if (record.
|
|
260
|
+
if (record.key === "$.foo[*]") {
|
|
229
261
|
// Type of record.value is number
|
|
230
262
|
} else {
|
|
231
263
|
// Type of record.value is string
|
|
@@ -235,11 +267,42 @@ await new ReadableStream({
|
|
|
235
267
|
);
|
|
236
268
|
```
|
|
237
269
|
|
|
238
|
-
> [!TIP]
|
|
239
|
-
> If you only want to validate some values, you can mix `{ path: JSONPath; schema: StandardSchemaV1 }` and `JSONPath` in the `jsonPaths` array.
|
|
240
|
-
|
|
241
270
|
For JSONPath queries with no schema, emitted values will have the `unknown` type.
|
|
242
271
|
|
|
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:
|
|
273
|
+
|
|
274
|
+
<!-- prettier-ignore -->
|
|
275
|
+
```ts
|
|
276
|
+
import * as z from "zod";
|
|
277
|
+
|
|
278
|
+
await new ReadableStream({
|
|
279
|
+
start(controller) {
|
|
280
|
+
controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
|
|
281
|
+
controller.close();
|
|
282
|
+
},
|
|
283
|
+
})
|
|
284
|
+
.pipeThrough(
|
|
285
|
+
new JSONParseStream([
|
|
286
|
+
{ key: "foo", path: "$.foo[*]", schema: z.number() },
|
|
287
|
+
{ key: "bar", path: "$.bar[*]", schema: z.string() },
|
|
288
|
+
]),
|
|
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
|
+
);
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
> [!TIP]
|
|
304
|
+
> 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.
|
|
305
|
+
|
|
243
306
|
## JSONPath
|
|
244
307
|
|
|
245
308
|
json-web-streams supports a subset of JSONPath. Currently the supported components are:
|
|
@@ -285,7 +348,7 @@ Let's say you have this JSON:
|
|
|
285
348
|
{ "foo": [1, 2], "bar": ["a", "b", "c"] }
|
|
286
349
|
```
|
|
287
350
|
|
|
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 `.
|
|
351
|
+
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
352
|
|
|
290
353
|
<!-- prettier-ignore -->
|
|
291
354
|
```ts
|
|
@@ -301,7 +364,7 @@ await new ReadableStream({
|
|
|
301
364
|
.pipeTo(
|
|
302
365
|
new WritableStream({
|
|
303
366
|
write(record) {
|
|
304
|
-
if (record.
|
|
367
|
+
if (record.key === "$.foo[*]") {
|
|
305
368
|
// 1, 2
|
|
306
369
|
} else {
|
|
307
370
|
// a, b, c
|
|
@@ -362,7 +425,7 @@ await new ReadableStream({
|
|
|
362
425
|
.pipeTo(
|
|
363
426
|
new WritableStream({
|
|
364
427
|
write(record) {
|
|
365
|
-
if (record.
|
|
428
|
+
if (record.key === "$.foo[*]") {
|
|
366
429
|
// 1, 2
|
|
367
430
|
} else {
|
|
368
431
|
// a, b, c
|
|
@@ -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,27 @@ 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
6
|
if (!y) return false;
|
|
15
|
-
if (x.type ===
|
|
16
|
-
if (
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return x.type === "wildcard";
|
|
7
|
+
if (x.type === "wildcard") {
|
|
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;
|
|
21
12
|
};
|
|
22
13
|
var JSONParseStream = class extends TransformStream {
|
|
23
14
|
_parser;
|
|
24
15
|
constructor(jsonPaths, options) {
|
|
25
16
|
let parser;
|
|
26
|
-
|
|
17
|
+
let minPathArrayLength = Infinity;
|
|
18
|
+
let maxPathArrayLength = -Infinity;
|
|
27
19
|
const jsonPathInfos = jsonPaths.map((row) => {
|
|
20
|
+
let key;
|
|
28
21
|
let path;
|
|
29
22
|
let schema;
|
|
30
23
|
if (typeof row === "string") path = row;
|
|
31
24
|
else {
|
|
25
|
+
key = row.key;
|
|
32
26
|
path = row.path;
|
|
33
27
|
schema = row.schema;
|
|
34
28
|
}
|
|
@@ -38,64 +32,96 @@ var JSONParseStream = class extends TransformStream {
|
|
|
38
32
|
if (wildcardIndexes === void 0) wildcardIndexes = [];
|
|
39
33
|
wildcardIndexes.push(i);
|
|
40
34
|
}
|
|
35
|
+
const matches = pathArray.length === 0 ? "yes" : "unknown";
|
|
36
|
+
if (pathArray.length > maxPathArrayLength) maxPathArrayLength = pathArray.length;
|
|
37
|
+
if (pathArray.length < minPathArrayLength) minPathArrayLength = pathArray.length;
|
|
41
38
|
return {
|
|
39
|
+
key,
|
|
40
|
+
matches,
|
|
42
41
|
path,
|
|
43
42
|
pathArray,
|
|
44
|
-
schema,
|
|
43
|
+
validate: schema?.["~standard"].validate,
|
|
45
44
|
wildcardIndexes
|
|
46
45
|
};
|
|
47
46
|
});
|
|
47
|
+
const jsonPathInfosThatMatch = new Set(jsonPathInfos.filter((info) => info.matches === "yes"));
|
|
48
|
+
const updateMatches = (type) => {
|
|
49
|
+
for (const info of jsonPathInfos) {
|
|
50
|
+
const pathArray = info.pathArray;
|
|
51
|
+
if ((info.matches === "unknown" && type === "push" || info.matches !== "unknown" && info.matches !== "noBeforeEnd" && type === "key") && parser.stack.length === pathArray.length) {
|
|
52
|
+
let pathMatches = "yes";
|
|
53
|
+
for (let j = 0; j < pathArray.length; j++) {
|
|
54
|
+
let stackComponent = parser.stack[j + 1];
|
|
55
|
+
if (!stackComponent) {
|
|
56
|
+
if (type === "key") stackComponent = parser;
|
|
57
|
+
else if (pathArray[j].type === "wildcard") {
|
|
58
|
+
pathMatches = "yes";
|
|
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
|
+
}
|
|
73
|
+
};
|
|
48
74
|
super({
|
|
49
75
|
start(controller) {
|
|
50
76
|
parser = new JSONParseStreamRaw({
|
|
51
|
-
multi,
|
|
77
|
+
multi: options?.multi,
|
|
78
|
+
onKey: (stackLength) => {
|
|
79
|
+
if (stackLength <= maxPathArrayLength && stackLength >= minPathArrayLength) updateMatches("key");
|
|
80
|
+
},
|
|
81
|
+
onPop: (stackLength) => {
|
|
82
|
+
if (stackLength < maxPathArrayLength && stackLength >= minPathArrayLength - 1) {
|
|
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");
|
|
91
|
+
},
|
|
52
92
|
onValue: (value) => {
|
|
53
|
-
const { key: parserKey, mode: parserMode, stack: parserStack, value: parserValue } = parser;
|
|
54
|
-
const stackLength = parserStack.length;
|
|
55
|
-
const stackPathArray = new Array(stackLength);
|
|
56
|
-
for (let i = 1; i < stackLength; i++) stackPathArray[i - 1] = stackToPathComponent(parserStack[i]);
|
|
57
|
-
if (parserStack.length > 0) stackPathArray[stackLength - 1] = stackToPathComponent({
|
|
58
|
-
value: parserValue,
|
|
59
|
-
key: parserKey,
|
|
60
|
-
mode: parserMode
|
|
61
|
-
});
|
|
62
93
|
let keep = false;
|
|
63
|
-
for (const { path, pathArray,
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (!wildcardKeys) wildcardKeys = [];
|
|
78
|
-
wildcardKeys.push(pathComponent.value);
|
|
79
|
-
}
|
|
94
|
+
for (const { key, path, pathArray, validate, wildcardIndexes } of jsonPathInfosThatMatch) if (parser.stack.length === pathArray.length) {
|
|
95
|
+
let valueToEmit;
|
|
96
|
+
if (validate) {
|
|
97
|
+
const result = validate(value);
|
|
98
|
+
if (result instanceof Promise) throw new TypeError("Schema validation must be synchronous");
|
|
99
|
+
if (result.issues) throw new Error(JSON.stringify(result.issues, null, 2));
|
|
100
|
+
valueToEmit = result.value;
|
|
101
|
+
} else valueToEmit = value;
|
|
102
|
+
let wildcardKeys;
|
|
103
|
+
if (wildcardIndexes) for (const index of wildcardIndexes) {
|
|
104
|
+
const stackComponent = parser.stack[index + 1] ?? parser;
|
|
105
|
+
if (stackComponent.mode === "OBJECT" && stackComponent.key !== void 0) {
|
|
106
|
+
if (!wildcardKeys) wildcardKeys = [];
|
|
107
|
+
wildcardKeys.push(stackComponent.key);
|
|
80
108
|
}
|
|
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
109
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
if (wildcardKeys) controller.enqueue({
|
|
111
|
+
key: key ?? path,
|
|
112
|
+
value: valueToEmit,
|
|
113
|
+
wildcardKeys
|
|
114
|
+
});
|
|
115
|
+
else controller.enqueue({
|
|
116
|
+
key: key ?? path,
|
|
117
|
+
value: valueToEmit
|
|
118
|
+
});
|
|
119
|
+
} else keep = true;
|
|
120
|
+
if (keep) return;
|
|
121
|
+
const type = typeof value;
|
|
122
|
+
if (!(type === "string" || type === "number" || type === "boolean" || value === null)) {
|
|
123
|
+
for (const row of parser.stack) row.value = void 0;
|
|
124
|
+
if (typeof parser.value === "object" && parser.value !== null && parser.key !== void 0) parser.value[parser.key] = void 0;
|
|
99
125
|
}
|
|
100
126
|
}
|
|
101
127
|
});
|
|
@@ -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,17 +21,26 @@ 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
29
|
highSurrogate: number | undefined;
|
|
26
30
|
seenRootObject: boolean;
|
|
27
|
-
multi: boolean;
|
|
31
|
+
multi: boolean | undefined;
|
|
28
32
|
multiIndex: number;
|
|
29
33
|
constructor({
|
|
30
34
|
multi,
|
|
35
|
+
onKey,
|
|
36
|
+
onPop,
|
|
37
|
+
onPush,
|
|
31
38
|
onValue
|
|
32
39
|
}: {
|
|
33
|
-
multi
|
|
40
|
+
multi?: boolean;
|
|
41
|
+
onKey?: OnPopPush;
|
|
42
|
+
onPop?: OnPopPush;
|
|
43
|
+
onPush?: OnPopPush;
|
|
34
44
|
onValue: OnValue;
|
|
35
45
|
});
|
|
36
46
|
charError(char: string, i: number): void;
|
|
@@ -38,6 +48,7 @@ declare class JSONParseStreamRaw {
|
|
|
38
48
|
write(text: string): void;
|
|
39
49
|
push(): void;
|
|
40
50
|
pop(): void;
|
|
51
|
+
setKey(key: number | string | undefined): void;
|
|
41
52
|
emit(value: Value): void;
|
|
42
53
|
onToken(token: Token, value: Value, i: number): void;
|
|
43
54
|
numberReviver(text: string, i: number): void;
|
|
@@ -14,15 +14,21 @@ var JSONParseStreamRaw = class {
|
|
|
14
14
|
key;
|
|
15
15
|
value;
|
|
16
16
|
position = 0;
|
|
17
|
+
onKey;
|
|
18
|
+
onPop;
|
|
19
|
+
onPush;
|
|
17
20
|
onValue;
|
|
18
21
|
unicode;
|
|
19
22
|
highSurrogate;
|
|
20
23
|
seenRootObject = false;
|
|
21
24
|
multi;
|
|
22
25
|
multiIndex = 0;
|
|
23
|
-
constructor({ multi, onValue }) {
|
|
24
|
-
this.onValue = onValue;
|
|
26
|
+
constructor({ multi, onKey, onPop, onPush, onValue }) {
|
|
25
27
|
this.multi = multi;
|
|
28
|
+
this.onKey = onKey;
|
|
29
|
+
this.onPop = onPop;
|
|
30
|
+
this.onPush = onPush;
|
|
31
|
+
this.onValue = onValue;
|
|
26
32
|
}
|
|
27
33
|
charError(char, i) {
|
|
28
34
|
throw new Error(`Unexpected ${JSON.stringify(char)} at position ${this.position + i} in state ${this.tokenizerState}`);
|
|
@@ -181,6 +187,7 @@ var JSONParseStreamRaw = class {
|
|
|
181
187
|
key: this.key,
|
|
182
188
|
mode: this.mode
|
|
183
189
|
});
|
|
190
|
+
this.onPush?.(this.stack.length);
|
|
184
191
|
}
|
|
185
192
|
pop() {
|
|
186
193
|
const value = this.value;
|
|
@@ -189,8 +196,13 @@ var JSONParseStreamRaw = class {
|
|
|
189
196
|
this.key = parent.key;
|
|
190
197
|
this.mode = parent.mode;
|
|
191
198
|
this.emit(value);
|
|
199
|
+
this.onPop?.(this.stack.length);
|
|
192
200
|
if (!this.mode) this.state = "VALUE";
|
|
193
201
|
}
|
|
202
|
+
setKey(key) {
|
|
203
|
+
this.key = key;
|
|
204
|
+
if (this.onKey && typeof key === "string" && this.mode === "OBJECT") this.onKey(this.stack.length);
|
|
205
|
+
}
|
|
194
206
|
emit(value) {
|
|
195
207
|
if (this.mode) this.state = "COMMA";
|
|
196
208
|
if (value === void 0) return;
|
|
@@ -207,14 +219,14 @@ var JSONParseStreamRaw = class {
|
|
|
207
219
|
this.push();
|
|
208
220
|
if (this.value) this.value = this.value[this.key] = {};
|
|
209
221
|
else this.value = {};
|
|
210
|
-
this.
|
|
222
|
+
this.setKey(void 0);
|
|
211
223
|
this.state = "KEY";
|
|
212
224
|
this.mode = "OBJECT";
|
|
213
225
|
} else if (token === "LEFT_BRACKET") {
|
|
214
226
|
this.push();
|
|
215
227
|
if (this.value) this.value = this.value[this.key] = [];
|
|
216
228
|
else this.value = [];
|
|
217
|
-
this.
|
|
229
|
+
this.setKey(0);
|
|
218
230
|
this.mode = "ARRAY";
|
|
219
231
|
this.state = "VALUE";
|
|
220
232
|
} else if (token === "RIGHT_BRACE") if (this.mode === "OBJECT" && this.state !== "VALUE_AFTER_COMMA") this.pop();
|
|
@@ -223,7 +235,7 @@ var JSONParseStreamRaw = class {
|
|
|
223
235
|
else return this.parseError(token, value, i);
|
|
224
236
|
else return this.parseError(token, value, i);
|
|
225
237
|
else if (this.state === "KEY" || this.state === "KEY_AFTER_COMMA") if (token === "STRING") {
|
|
226
|
-
this.
|
|
238
|
+
this.setKey(value);
|
|
227
239
|
this.state = "COLON";
|
|
228
240
|
} else if (token === "RIGHT_BRACE" && this.state !== "KEY_AFTER_COMMA") this.pop();
|
|
229
241
|
else return this.parseError(token, value, i);
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "json-web-streams",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.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",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"dist"
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
|
+
"bench": "vitest bench",
|
|
24
25
|
"build": "tsdown --unbundle",
|
|
25
26
|
"format": "prettier --write .",
|
|
26
27
|
"lint": "npm-run-all --parallel lint:oxlint lint:tsc",
|
|
@@ -39,15 +40,15 @@
|
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
42
|
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
|
|
42
|
-
"@types/node": "^24.
|
|
43
|
+
"@types/node": "^24.9.1",
|
|
43
44
|
"husky": "^9.1.7",
|
|
44
|
-
"lint-staged": "^16.2.
|
|
45
|
+
"lint-staged": "^16.2.5",
|
|
45
46
|
"npm-run-all2": "^8.0.4",
|
|
46
47
|
"oxlint": "^1.23.0",
|
|
47
48
|
"oxlint-tsgolint": "^0.2.0",
|
|
48
49
|
"prettier": "^3.6.2",
|
|
49
50
|
"prettier-plugin-packagejson": "^2.5.19",
|
|
50
|
-
"tsdown": "^0.15.
|
|
51
|
+
"tsdown": "^0.15.9",
|
|
51
52
|
"typescript": "^5.9.3",
|
|
52
53
|
"vitest": "^3.2.4",
|
|
53
54
|
"zod": "^4.1.12"
|