123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- 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; // \X
- }
- if (code < 0x20) {
- return 6; // \uXXXX
- }
- return code < 128 ? 1 : 2; // UTF8 bytes
- });
- 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; // \uXXXX since no pair with trailing surrogate yet
- prevLeadingSurrogate = true;
- continue;
- } else if (isTrailingSurrogate(code)) {
- len = prevLeadingSurrogate
- ? len - 2 // surrogate pair (4 bytes), since we calculate prev leading surrogate as 6 bytes, substruct 2 bytes
- : len + 6; // \uXXXX
- } else {
- len += 3; // code >= 2048 is 3 bytes length for UTF8
- }
- prevLeadingSurrogate = false;
- }
- return len + 2; // +2 for quotes
- }
- function primitiveLength(value) {
- switch (typeof value) {
- case 'string':
- return stringLength(value);
- case 'number':
- return Number.isFinite(value) ? String(value).length : 4 /* null */;
- case 'boolean':
- return value ? 4 /* true */ : 5 /* false */;
- case 'undefined':
- case 'object':
- return 4; /* null */
- 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);
- // check for circular structure
- if (type !== PRIMITIVE && stack.has(value)) {
- circular.add(value);
- length += 4; // treat as null
- 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; // FIXME: that's the length of undefined, should we normalize behaviour to convert it to null?
- }
- 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) {
- // value is printed
- length += stringLength(key) + 1; // "key":
- entries++;
- }
- }
- }
- if (entries > 1) {
- length += entries - 1; // commas
- }
- stack.delete(value);
- if (space > 0 && entries > 0) {
- length += (1 + (stack.size + 1) * space + 1) * entries; // for each key-value: \n{space}
- length += 1 + stack.size * space; // for }
- }
- 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; // commas
- }
- stack.delete(value);
- if (space > 0 && value.length > 0) {
- length += (1 + (stack.size + 1) * space) * value.length; // for each element: \n{space}
- length += 1 + stack.size * space; // for ]
- }
- 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]
- };
- };
|