176 lines
4.5 KiB
JavaScript
176 lines
4.5 KiB
JavaScript
'use strict';
|
|
|
|
const utils = require('./utils.cjs');
|
|
|
|
function encodeString(value) {
|
|
if (/[^\x20\x21\x23-\x5B\x5D-\uD799]/.test(value)) { // [^\x20-\uD799]|[\x22\x5c]
|
|
return JSON.stringify(value);
|
|
}
|
|
|
|
return '"' + value + '"';
|
|
}
|
|
|
|
function* stringifyChunked(value, ...args) {
|
|
const { replacer, getKeys, space, ...options } = utils.normalizeStringifyOptions(...args);
|
|
const highWaterMark = Number(options.highWaterMark) || 0x4000; // 16kb by default
|
|
|
|
const keyStrings = new Map();
|
|
const stack = [];
|
|
const rootValue = { '': value };
|
|
let prevState = null;
|
|
let state = () => printEntry('', value);
|
|
let stateValue = rootValue;
|
|
let stateEmpty = true;
|
|
let stateKeys = [''];
|
|
let stateIndex = 0;
|
|
let buffer = '';
|
|
|
|
while (true) {
|
|
state();
|
|
|
|
if (buffer.length >= highWaterMark || prevState === null) {
|
|
// flush buffer
|
|
yield buffer;
|
|
buffer = '';
|
|
|
|
if (prevState === null) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function printObject() {
|
|
if (stateIndex === 0) {
|
|
stateKeys = getKeys(stateValue);
|
|
buffer += '{';
|
|
}
|
|
|
|
// when no keys left
|
|
if (stateIndex === stateKeys.length) {
|
|
buffer += space && !stateEmpty
|
|
? `\n${space.repeat(stack.length - 1)}}`
|
|
: '}';
|
|
|
|
popState();
|
|
return;
|
|
}
|
|
|
|
const key = stateKeys[stateIndex++];
|
|
printEntry(key, stateValue[key]);
|
|
}
|
|
|
|
function printArray() {
|
|
if (stateIndex === 0) {
|
|
buffer += '[';
|
|
}
|
|
|
|
if (stateIndex === stateValue.length) {
|
|
buffer += space && !stateEmpty
|
|
? `\n${space.repeat(stack.length - 1)}]`
|
|
: ']';
|
|
|
|
popState();
|
|
return;
|
|
}
|
|
|
|
printEntry(stateIndex, stateValue[stateIndex++]);
|
|
}
|
|
|
|
function printEntryPrelude(key) {
|
|
if (stateEmpty) {
|
|
stateEmpty = false;
|
|
} else {
|
|
buffer += ',';
|
|
}
|
|
|
|
if (space && prevState !== null) {
|
|
buffer += `\n${space.repeat(stack.length)}`;
|
|
}
|
|
|
|
if (state === printObject) {
|
|
let keyString = keyStrings.get(key);
|
|
|
|
if (keyString === undefined) {
|
|
keyStrings.set(key, keyString = encodeString(key) + (space ? ': ' : ':'));
|
|
}
|
|
|
|
buffer += keyString;
|
|
}
|
|
}
|
|
|
|
function printEntry(key, value) {
|
|
value = utils.replaceValue(stateValue, key, value, replacer);
|
|
|
|
if (value === null || typeof value !== 'object') {
|
|
// primitive
|
|
if (state !== printObject || value !== undefined) {
|
|
printEntryPrelude(key);
|
|
pushPrimitive(value);
|
|
}
|
|
} else {
|
|
// If the visited set does not change after adding a value, then it is already in the set
|
|
if (stack.includes(value)) {
|
|
throw new TypeError('Converting circular structure to JSON');
|
|
}
|
|
|
|
printEntryPrelude(key);
|
|
stack.push(value);
|
|
|
|
pushState();
|
|
state = Array.isArray(value) ? printArray : printObject;
|
|
stateValue = value;
|
|
stateEmpty = true;
|
|
stateIndex = 0;
|
|
}
|
|
}
|
|
|
|
function pushPrimitive(value) {
|
|
switch (typeof value) {
|
|
case 'string':
|
|
buffer += encodeString(value);
|
|
break;
|
|
|
|
case 'number':
|
|
buffer += Number.isFinite(value) ? String(value) : 'null';
|
|
break;
|
|
|
|
case 'boolean':
|
|
buffer += value ? 'true' : 'false';
|
|
break;
|
|
|
|
case 'undefined':
|
|
case 'object': // typeof null === 'object'
|
|
buffer += 'null';
|
|
break;
|
|
|
|
default:
|
|
throw new TypeError(`Do not know how to serialize a ${value.constructor?.name || typeof value}`);
|
|
}
|
|
}
|
|
|
|
function pushState() {
|
|
prevState = {
|
|
keys: stateKeys,
|
|
index: stateIndex,
|
|
prev: prevState
|
|
};
|
|
}
|
|
|
|
function popState() {
|
|
stack.pop();
|
|
const value = stack.length > 0 ? stack[stack.length - 1] : rootValue;
|
|
|
|
// restore state
|
|
state = Array.isArray(value) ? printArray : printObject;
|
|
stateValue = value;
|
|
stateEmpty = false;
|
|
stateKeys = prevState.keys;
|
|
stateIndex = prevState.index;
|
|
|
|
// pop state
|
|
prevState = prevState.prev;
|
|
}
|
|
}
|
|
|
|
exports.stringifyChunked = stringifyChunked;
|