1 | import { normalizeStringifyOptions, replaceValue } from './utils.js';
|
---|
2 |
|
---|
3 | function encodeString(value) {
|
---|
4 | if (/[^\x20\x21\x23-\x5B\x5D-\uD799]/.test(value)) { // [^\x20-\uD799]|[\x22\x5c]
|
---|
5 | return JSON.stringify(value);
|
---|
6 | }
|
---|
7 |
|
---|
8 | return '"' + value + '"';
|
---|
9 | }
|
---|
10 |
|
---|
11 | export function* stringifyChunked(value, ...args) {
|
---|
12 | const { replacer, getKeys, space, ...options } = normalizeStringifyOptions(...args);
|
---|
13 | const highWaterMark = Number(options.highWaterMark) || 0x4000; // 16kb by default
|
---|
14 |
|
---|
15 | const keyStrings = new Map();
|
---|
16 | const stack = [];
|
---|
17 | const rootValue = { '': value };
|
---|
18 | let prevState = null;
|
---|
19 | let state = () => printEntry('', value);
|
---|
20 | let stateValue = rootValue;
|
---|
21 | let stateEmpty = true;
|
---|
22 | let stateKeys = [''];
|
---|
23 | let stateIndex = 0;
|
---|
24 | let buffer = '';
|
---|
25 |
|
---|
26 | while (true) {
|
---|
27 | state();
|
---|
28 |
|
---|
29 | if (buffer.length >= highWaterMark || prevState === null) {
|
---|
30 | // flush buffer
|
---|
31 | yield buffer;
|
---|
32 | buffer = '';
|
---|
33 |
|
---|
34 | if (prevState === null) {
|
---|
35 | break;
|
---|
36 | }
|
---|
37 | }
|
---|
38 | }
|
---|
39 |
|
---|
40 | function printObject() {
|
---|
41 | if (stateIndex === 0) {
|
---|
42 | stateKeys = getKeys(stateValue);
|
---|
43 | buffer += '{';
|
---|
44 | }
|
---|
45 |
|
---|
46 | // when no keys left
|
---|
47 | if (stateIndex === stateKeys.length) {
|
---|
48 | buffer += space && !stateEmpty
|
---|
49 | ? `\n${space.repeat(stack.length - 1)}}`
|
---|
50 | : '}';
|
---|
51 |
|
---|
52 | popState();
|
---|
53 | return;
|
---|
54 | }
|
---|
55 |
|
---|
56 | const key = stateKeys[stateIndex++];
|
---|
57 | printEntry(key, stateValue[key]);
|
---|
58 | }
|
---|
59 |
|
---|
60 | function printArray() {
|
---|
61 | if (stateIndex === 0) {
|
---|
62 | buffer += '[';
|
---|
63 | }
|
---|
64 |
|
---|
65 | if (stateIndex === stateValue.length) {
|
---|
66 | buffer += space && !stateEmpty
|
---|
67 | ? `\n${space.repeat(stack.length - 1)}]`
|
---|
68 | : ']';
|
---|
69 |
|
---|
70 | popState();
|
---|
71 | return;
|
---|
72 | }
|
---|
73 |
|
---|
74 | printEntry(stateIndex, stateValue[stateIndex++]);
|
---|
75 | }
|
---|
76 |
|
---|
77 | function printEntryPrelude(key) {
|
---|
78 | if (stateEmpty) {
|
---|
79 | stateEmpty = false;
|
---|
80 | } else {
|
---|
81 | buffer += ',';
|
---|
82 | }
|
---|
83 |
|
---|
84 | if (space && prevState !== null) {
|
---|
85 | buffer += `\n${space.repeat(stack.length)}`;
|
---|
86 | }
|
---|
87 |
|
---|
88 | if (state === printObject) {
|
---|
89 | let keyString = keyStrings.get(key);
|
---|
90 |
|
---|
91 | if (keyString === undefined) {
|
---|
92 | keyStrings.set(key, keyString = encodeString(key) + (space ? ': ' : ':'));
|
---|
93 | }
|
---|
94 |
|
---|
95 | buffer += keyString;
|
---|
96 | }
|
---|
97 | }
|
---|
98 |
|
---|
99 | function printEntry(key, value) {
|
---|
100 | value = replaceValue(stateValue, key, value, replacer);
|
---|
101 |
|
---|
102 | if (value === null || typeof value !== 'object') {
|
---|
103 | // primitive
|
---|
104 | if (state !== printObject || value !== undefined) {
|
---|
105 | printEntryPrelude(key);
|
---|
106 | pushPrimitive(value);
|
---|
107 | }
|
---|
108 | } else {
|
---|
109 | // If the visited set does not change after adding a value, then it is already in the set
|
---|
110 | if (stack.includes(value)) {
|
---|
111 | throw new TypeError('Converting circular structure to JSON');
|
---|
112 | }
|
---|
113 |
|
---|
114 | printEntryPrelude(key);
|
---|
115 | stack.push(value);
|
---|
116 |
|
---|
117 | pushState();
|
---|
118 | state = Array.isArray(value) ? printArray : printObject;
|
---|
119 | stateValue = value;
|
---|
120 | stateEmpty = true;
|
---|
121 | stateIndex = 0;
|
---|
122 | }
|
---|
123 | }
|
---|
124 |
|
---|
125 | function pushPrimitive(value) {
|
---|
126 | switch (typeof value) {
|
---|
127 | case 'string':
|
---|
128 | buffer += encodeString(value);
|
---|
129 | break;
|
---|
130 |
|
---|
131 | case 'number':
|
---|
132 | buffer += Number.isFinite(value) ? String(value) : 'null';
|
---|
133 | break;
|
---|
134 |
|
---|
135 | case 'boolean':
|
---|
136 | buffer += value ? 'true' : 'false';
|
---|
137 | break;
|
---|
138 |
|
---|
139 | case 'undefined':
|
---|
140 | case 'object': // typeof null === 'object'
|
---|
141 | buffer += 'null';
|
---|
142 | break;
|
---|
143 |
|
---|
144 | default:
|
---|
145 | throw new TypeError(`Do not know how to serialize a ${value.constructor?.name || typeof value}`);
|
---|
146 | }
|
---|
147 | }
|
---|
148 |
|
---|
149 | function pushState() {
|
---|
150 | prevState = {
|
---|
151 | keys: stateKeys,
|
---|
152 | index: stateIndex,
|
---|
153 | prev: prevState
|
---|
154 | };
|
---|
155 | }
|
---|
156 |
|
---|
157 | function popState() {
|
---|
158 | stack.pop();
|
---|
159 | const value = stack.length > 0 ? stack[stack.length - 1] : rootValue;
|
---|
160 |
|
---|
161 | // restore state
|
---|
162 | state = Array.isArray(value) ? printArray : printObject;
|
---|
163 | stateValue = value;
|
---|
164 | stateEmpty = false;
|
---|
165 | stateKeys = prevState.keys;
|
---|
166 | stateIndex = prevState.index;
|
---|
167 |
|
---|
168 | // pop state
|
---|
169 | prevState = prevState.prev;
|
---|
170 | }
|
---|
171 | };
|
---|