const {
normalizeReplacer,
normalizeSpace,
replaceValue,
getTypeNative,
getTypeAsync,
isLeadingSurrogate,
isTrailingSurrogate,
escapableCharCodeSubstitution,
type: {
PRIMITIVE,
OBJECT,
ARRAY,
PROMISE,
STRING_STREAM,
OBJECT_STREAM
}
} = require('./utils');
const charLength2048 = Array.from({ length: 2048 }).map((_, code) => {
if (escapableCharCodeSubstitution.hasOwnProperty(code)) {
return 2; }
if (code < 0x20) {
return 6; }
return code < 128 ? 1 : 2; });
function stringLength(str) {
let len = 0;
let prevLeadingSurrogate = false;
for (let i = 0; i < str.length; i++) {
const code = str.charCodeAt(i);
if (code < 2048) {
len += charLength2048[code];
} else if (isLeadingSurrogate(code)) {
len += 6; prevLeadingSurrogate = true;
continue;
} else if (isTrailingSurrogate(code)) {
len = prevLeadingSurrogate
? len - 2 : len + 6; } else {
len += 3; }
prevLeadingSurrogate = false;
}
return len + 2; }
function primitiveLength(value) {
switch (typeof value) {
case 'string':
return stringLength(value);
case 'number':
return Number.isFinite(value) ? String(value).length : 4 ;
case 'boolean':
return value ? 4 : 5 ;
case 'undefined':
case 'object':
return 4;
default:
return 0;
}
}
function spaceLength(space) {
space = normalizeSpace(space);
return typeof space === 'string' ? space.length : 0;
}
module.exports = function jsonStringifyInfo(value, replacer, space, options) {
function walk(holder, key, value) {
if (stop) {
return;
}
value = replaceValue(holder, key, value, replacer);
let type = getType(value);
if (type !== PRIMITIVE && stack.has(value)) {
circular.add(value);
length += 4;
if (!options.continueOnCircular) {
stop = true;
}
return;
}
switch (type) {
case PRIMITIVE:
if (value !== undefined || Array.isArray(holder)) {
length += primitiveLength(value);
} else if (holder === root) {
length += 9; }
break;
case OBJECT: {
if (visited.has(value)) {
duplicate.add(value);
length += visited.get(value);
break;
}
const valueLength = length;
let entries = 0;
length += 2;
stack.add(value);
for (const key in value) {
if (hasOwnProperty.call(value, key) && (allowlist === null || allowlist.has(key))) {
const prevLength = length;
walk(value, key, value[key]);
if (prevLength !== length) {
length += stringLength(key) + 1; entries++;
}
}
}
if (entries > 1) {
length += entries - 1; }
stack.delete(value);
if (space > 0 && entries > 0) {
length += (1 + (stack.size + 1) * space + 1) * entries; length += 1 + stack.size * space; }
visited.set(value, length - valueLength);
break;
}
case ARRAY: {
if (visited.has(value)) {
duplicate.add(value);
length += visited.get(value);
break;
}
const valueLength = length;
length += 2;
stack.add(value);
for (let i = 0; i < value.length; i++) {
walk(value, i, value[i]);
}
if (value.length > 1) {
length += value.length - 1; }
stack.delete(value);
if (space > 0 && value.length > 0) {
length += (1 + (stack.size + 1) * space) * value.length; length += 1 + stack.size * space; }
visited.set(value, length - valueLength);
break;
}
case PROMISE:
case STRING_STREAM:
async.add(value);
break;
case OBJECT_STREAM:
length += 2; async.add(value);
break;
}
}
let allowlist = null;
replacer = normalizeReplacer(replacer);
if (Array.isArray(replacer)) {
allowlist = new Set(replacer);
replacer = null;
}
space = spaceLength(space);
options = options || {};
const visited = new Map();
const stack = new Set();
const duplicate = new Set();
const circular = new Set();
const async = new Set();
const getType = options.async ? getTypeAsync : getTypeNative;
const root = { '': value };
let stop = false;
let length = 0;
walk(root, '', value);
return {
minLength: isNaN(length) ? Infinity : length,
circular: [...circular],
duplicate: [...duplicate],
async: [...async]
};
};