export default function jsonStringify(value) {
return serialize(value, [], '', false);
}
function serialize(value, ancestors, key, inArray) {
if (value !== null && typeof value === 'object' && typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
const valueType = typeof value;
if (valueType === 'bigint') {
throw new TypeError('Do not know how to serialize a BigInt');
}
if (valueType === 'string') {
return quoteString(value);
}
if (valueType === 'number') {
return Number.isFinite(value) ? String(value) : 'null';
}
if (valueType === 'boolean') {
return value ? 'true' : 'false';
}
if (valueType === 'undefined' || valueType === 'function' || valueType === 'symbol') {
return inArray ? 'null' : undefined;
}
if (value === null) {
return 'null';
}
if (value instanceof Number || value instanceof String || value instanceof Boolean) {
return serialize(value.valueOf(), ancestors, key, inArray);
}
if (ancestors.includes(value)) {
throw new TypeError('Converting circular structure to JSON');
}
ancestors.push(value);
if (Array.isArray(value)) {
const items = [];
for (let i = 0; i < value.length; i++) {
const item = serialize(value[i], ancestors, String(i), true);
items.push(item === undefined ? 'null' : item);
}
ancestors.pop();
return `[${items.join(',')}]`;
}
const pairs = [];
const keys = Object.keys(value);
for (const objectKey of keys) {
const serialized = serialize(value[objectKey], ancestors, objectKey, false);
if (serialized !== undefined) {
pairs.push(`${quoteString(objectKey)}:${serialized}`);
}
}
ancestors.pop();
return `{${pairs.join(',')}}`;
}
function quoteString(value) {
return `"${value.replace(/[\\"\u0000-\u001f]/g, (char) => {
switch (char) {
case '"':
return '\\"';
case '\\':
return '\\\\';
case '\b':
return '\\b';
case '\f':
return '\\f';
case '\n':
return '\\n';
case '\r':
return '\\r';
case '\t':
return '\\t';
default:
return `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`;
}
})}"`;
}