2017-06-08 00:10:44 +05:30
|
|
|
const STRING_MAX_LENGTH = 128 * 1024;
|
2017-03-29 11:16:20 +05:30
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a copy of any object without non-serializable elements to make result safe for JSON.stringify().
|
|
|
|
* Guaranteed to never throw.
|
|
|
|
*
|
|
|
|
* @param {any} obj Any data structure
|
|
|
|
* @param {object} options
|
2019-12-07 16:58:52 +05:30
|
|
|
* @param {Function=} options.filter - callback that is called on every object's key with (key,value) and should return
|
2017-03-29 11:16:20 +05:30
|
|
|
* value to use (may return undefined to remove unwanted keys). See nodeFilter and browserFilter.
|
2019-12-07 16:58:52 +05:30
|
|
|
* @param {number=} options.depth - maximum recursion depth. Elements deeper than that are stringified with util.inspect()
|
|
|
|
* @param {number=} options.maxSize - roughly maximum allowed size of data after JSON serialisation (but it's not guaranteed
|
2017-03-29 11:16:20 +05:30
|
|
|
* that it won't exceed the limit)
|
|
|
|
*
|
|
|
|
* @see https://github.com/ftlabs/js-abbreviate
|
2017-06-08 00:10:44 +05:30
|
|
|
*
|
2019-11-27 14:33:32 +05:30
|
|
|
* @returns {object}
|
2017-03-29 11:16:20 +05:30
|
|
|
*/
|
2017-04-12 00:55:27 +05:30
|
|
|
function abbreviate(obj, options = {}) {
|
2019-11-27 14:33:32 +05:30
|
|
|
const filter =
|
|
|
|
options.filter ||
|
|
|
|
function(key, value) {
|
|
|
|
return value;
|
|
|
|
};
|
|
|
|
const maxDepth = options.depth || 10;
|
|
|
|
const maxSize = options.maxSize || 1 * 1024 * 1024;
|
|
|
|
|
|
|
|
return abbreviateRecursive(
|
|
|
|
undefined,
|
|
|
|
obj,
|
|
|
|
filter,
|
|
|
|
{ sizeLeft: maxSize },
|
|
|
|
maxDepth,
|
|
|
|
);
|
2017-03-29 11:16:20 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
function limitStringLength(str) {
|
2019-11-27 14:33:32 +05:30
|
|
|
if (str.length > STRING_MAX_LENGTH) {
|
|
|
|
return `${str.substring(0, STRING_MAX_LENGTH / 2)} … ${str.substring(
|
|
|
|
str.length - STRING_MAX_LENGTH / 2,
|
|
|
|
)}`;
|
|
|
|
}
|
2017-06-08 00:10:44 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
return str;
|
2017-03-29 11:16:20 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
function abbreviateRecursive(key, obj, filter, state, maxDepth) {
|
2019-11-27 14:33:32 +05:30
|
|
|
if (state.sizeLeft < 0) {
|
|
|
|
return '**skipped**';
|
|
|
|
}
|
2017-03-29 11:16:20 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
state.sizeLeft -= 5; // rough approximation of JSON overhead
|
2017-03-29 11:16:20 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
obj = filter(key, obj);
|
2017-03-29 11:16:20 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
try {
|
|
|
|
switch (typeof obj) {
|
|
|
|
case 'object': {
|
|
|
|
if (obj === null) {
|
|
|
|
return null;
|
|
|
|
}
|
2017-06-08 00:10:44 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
if (maxDepth < 0) {
|
|
|
|
break; // fall back to stringification
|
|
|
|
}
|
2017-06-08 00:10:44 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
const newobj = Array.isArray(obj) ? [] : {};
|
2017-06-08 00:10:44 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
for (const i in obj) {
|
|
|
|
if (!obj.hasOwnProperty(i)) {
|
|
|
|
continue;
|
|
|
|
}
|
2017-03-29 11:16:20 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
newobj[i] = abbreviateRecursive(
|
|
|
|
i,
|
|
|
|
obj[i],
|
|
|
|
filter,
|
|
|
|
state,
|
|
|
|
maxDepth - 1,
|
|
|
|
);
|
2017-03-29 11:16:20 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
if (state.sizeLeft < 0) {
|
|
|
|
break;
|
|
|
|
}
|
2017-03-29 11:16:20 +05:30
|
|
|
}
|
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
return newobj;
|
|
|
|
}
|
|
|
|
|
|
|
|
case 'string':
|
|
|
|
obj = limitStringLength(obj);
|
2017-03-29 11:16:20 +05:30
|
|
|
state.sizeLeft -= obj.length;
|
2019-11-27 14:33:32 +05:30
|
|
|
|
|
|
|
return obj;
|
|
|
|
|
|
|
|
case 'number':
|
|
|
|
case 'boolean':
|
|
|
|
case 'undefined':
|
|
|
|
default:
|
2017-03-29 11:16:20 +05:30
|
|
|
return obj;
|
|
|
|
}
|
2019-11-27 14:33:32 +05:30
|
|
|
} catch (err) {
|
|
|
|
/* fall back to inspect*/
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
obj = limitStringLength(`${obj}`);
|
|
|
|
state.sizeLeft -= obj.length;
|
|
|
|
|
|
|
|
return obj;
|
|
|
|
} catch (err) {
|
|
|
|
return '**non-serializable**';
|
|
|
|
}
|
2017-03-29 11:16:20 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
function commonFilter(key, val) {
|
2019-11-27 14:33:32 +05:30
|
|
|
if (typeof val === 'function') {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (val instanceof Date) {
|
|
|
|
return `**Date** ${val}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (val instanceof Error) {
|
|
|
|
const err = {
|
|
|
|
// These properties are implemented as magical getters and don't show up in for in
|
|
|
|
stack: val.stack,
|
|
|
|
message: val.message,
|
|
|
|
name: val.name,
|
|
|
|
};
|
|
|
|
|
|
|
|
for (const i in val) {
|
|
|
|
if (!val.hasOwnProperty(i)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
err[i] = val[i];
|
2017-03-29 11:16:20 +05:30
|
|
|
}
|
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
return err;
|
|
|
|
}
|
2017-04-12 00:55:27 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
return val;
|
2017-03-29 11:16:20 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
function nodeFilter(key, val) {
|
2019-11-27 14:33:32 +05:30
|
|
|
// domain objects are huge and have circular references
|
|
|
|
if (key === 'domain' && typeof val === 'object' && val._events) {
|
|
|
|
return '**domain ignored**';
|
|
|
|
}
|
2017-03-29 11:16:20 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
if (key === 'domainEmitter') {
|
|
|
|
return '**domainEmitter ignored**';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (val === global) {
|
|
|
|
return '**global**';
|
|
|
|
}
|
2017-03-29 11:16:20 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
return commonFilter(key, val);
|
2017-03-29 11:16:20 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
function browserFilter(key, val) {
|
2019-11-27 14:33:32 +05:30
|
|
|
if (val === window) {
|
|
|
|
return '**window**';
|
|
|
|
}
|
2017-03-29 11:16:20 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
if (val === document) {
|
|
|
|
return '**document**';
|
|
|
|
}
|
2017-03-29 11:16:20 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
if (val instanceof HTMLElement) {
|
|
|
|
const { outerHTML } = val;
|
2017-06-08 00:10:44 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
if (typeof outerHTML !== 'undefined') {
|
|
|
|
return `**HTMLElement** ${outerHTML}`;
|
2017-03-29 11:16:20 +05:30
|
|
|
}
|
2019-11-27 14:33:32 +05:30
|
|
|
}
|
2017-03-29 11:16:20 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
return commonFilter(key, val);
|
2017-03-29 11:16:20 +05:30
|
|
|
}
|
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
export { abbreviate, nodeFilter, browserFilter };
|
2017-03-29 11:16:20 +05:30
|
|
|
|
|
|
|
export default function(obj) {
|
2019-11-27 14:33:32 +05:30
|
|
|
return abbreviate(obj, {
|
|
|
|
filter: browserFilter,
|
|
|
|
});
|
2017-04-12 00:55:27 +05:30
|
|
|
}
|