1
0
mirror of synced 2025-12-10 00:17:58 +08:00

Dev: Support circular object

This commit is contained in:
surunzi
2016-10-12 21:14:50 +08:00
parent 85058afd02
commit dbd190c133
7 changed files with 191 additions and 226 deletions

View File

@@ -6,6 +6,7 @@ draggabilly
eruda
focusin
iframe
iteratee
onerror
postcss
scss

6
eustia/escapeJsonStr.js Normal file
View File

@@ -0,0 +1,6 @@
function exports(str)
{
return str.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\f|\n|\r|\t/g, '');
}

View File

@@ -33,7 +33,7 @@
}
.json {
background: #fff;
padding: 0 25px 10px;
padding: $padding;
}
.http {
.breadcrumb {

View File

@@ -8,22 +8,31 @@ export default class JsonViewer
this._data = [data];
this._$el = $el;
this._map = {};
this._appendTpl();
this._bindEvent();
}
_appendTpl()
{
this._$el.html(jsonToHtml(this._data, true));
this._$el.html(jsonToHtml(this._data, this._map, true));
}
_bindEvent()
{
var map = this._map;
this._$el.on('click', 'li', function (e)
{
var $this = util.$(this),
circularId = $this.data('circular'),
$firstSpan = util.$(this).find('span').eq(0);
if ($this.data('first-level') === 'true') return;
if ($this.data('first-level')) return;
if (circularId)
{
$this.find('ul').html(jsonToHtml(map[circularId], map, false));
$this.rmAttr('data-circular');
}
if (!$firstSpan.hasClass('eruda-expanded')) return;
@@ -43,33 +52,44 @@ export default class JsonViewer
}
}
function jsonToHtml(data, firstLevel)
function jsonToHtml(data, map, firstLevel)
{
var ret = '';
for (let key in data)
{
if (key === 'erudaObjAbstract') continue;
if (Object.hasOwnProperty.call(data, key)) ret += createEl(key, data[key], firstLevel);
let val = data[key];
if (key === 'erudaObjAbstract' ||
key === 'erudaCircular' ||
key === 'erudaId' ||
(util.isStr(val) && util.startWith(val, 'erudaJson'))) continue;
if (Object.hasOwnProperty.call(data, key)) ret += createEl(key, val, map, firstLevel);
}
return ret;
}
function createEl(key, val, firstLevel)
function createEl(key, val, map, firstLevel = false)
{
let type = 'object',
isUnenumerable = false;
isUnenumerable = false,
id;
if (key === 'erudaProto') key = '__proto__';
if (key === 'erudaId') return `<li id="${val}" class="eruda-hidden"></li>`;
if (util.startWith(key, 'erudaUnenumerable'))
{
key = util.trim(key.replace('erudaUnenumerable', ''));
isUnenumerable = true;
}
if (util.isArr(val)) type = 'array';
if (util.isArr(val))
{
type = 'array';
let lastVal = util.last(val);
if (util.isStr(val) && util.startWith(lastVal, 'erudaJson')) id = lastVal;
}
function wrapKey(key)
{
@@ -90,14 +110,17 @@ function createEl(key, val, firstLevel)
}
if (util.isObj(val))
{
if (val.erudaId) id = val.erudaId;
let circularId = val.erudaCircular;
if (id) map[id] = val;
var objAbstract = val['erudaObjAbstract'] || util.upperFirst(type);
var obj = `<li data-first-level="${firstLevel}">
<span class="eruda-expanded ${firstLevel ? '' : 'eruda-collapsed'}"></span>
var obj = `<li ${firstLevel ? 'data-first-level="true"' : ''} ${circularId ? 'data-circular="' + circularId + '"' : ''}>
<span class="${firstLevel ? '' : 'eruda-expanded eruda-collapsed'}"></span>
${wrapKey(key)}
<span class="eruda-open">${firstLevel ? '' : objAbstract}</span>
<ul class="eruda-${type}" ${firstLevel ? '' : 'style="display:none"'}>`;
obj += jsonToHtml(val);
obj += jsonToHtml(val, map);
return obj + `</ul><span class="eruda-close"></span></li>`;
}
@@ -108,10 +131,6 @@ function createEl(key, val, firstLevel)
<span class="eruda-${typeof val}">${encode(val)}</span>
</li>`;
}
if (util.isStr(val) && util.startWith(val, 'erudaJson'))
{
return `<li id="${val}" class="eruda-hidden"></li>`;
}
if (util.isStr(val) && util.startWith(val, 'function'))
{
return `<li>
@@ -127,12 +146,6 @@ function createEl(key, val, firstLevel)
</li>`;
}
/*if (util.isStr(val) && util.startWith(val, '[circular]'))
{
let id = util.last(val.split(' '));
return `<li class="eruda-circular" class="eruda-hidden" data-id="${id}"></li>`;
}*/
return `<li>
${wrapKey(key)}
<span class="eruda-${typeof val}">"${encode(val)}"</span>

View File

@@ -3,7 +3,6 @@ import util from './util'
// Modified from: https://jsconsole.com/
export default function getAbstract(obj, {
visited = [],
topObj,
level = 0,
getterVal = false,
@@ -17,16 +16,12 @@ export default function getAbstract(obj, {
names = [],
objEllipsis = '',
circular = false,
i, len;
i;
topObj = topObj || obj;
let passOpts = {
visited, getterVal,
unenumerable,
level: level + 1
};
let doStringify = level === 0;
let passOpts = {getterVal, unenumerable, level: level + 1},
doStringify = level === 0;
let keyWrapper = '<span style="color: #a71d5d;">',
fnWrapper = '<span style="color: #a71d5d;">',
@@ -67,6 +62,31 @@ export default function getAbstract(obj, {
return strWrapper + strEscape(`"${str}"`) + wrapperEnd;
}
function objIteratee(name)
{
if (i > keyNum)
{
objEllipsis = '...';
return;
}
let key = wrapKey(util.escapeJsonStr(name));
if (!getterVal)
{
let descriptor = Object.getOwnPropertyDescriptor(obj, name);
if (descriptor.get)
{
parts.push(`${key}: ${wrapStr('(...)')}`);
i++;
return;
}
}
if (typeof topObj[name] === 'function') return;
parts.push(`${key}: ${getAbstract(topObj[name], passOpts)}`);
i++;
}
try {
type = ({}).toString.call(obj);
} catch (e)
@@ -81,25 +101,14 @@ export default function getAbstract(obj, {
isSymbol = (type == '[object Symbol]'),
isBool = (type == '[object Boolean]');
for (i = 0, len = visited.length; i < len; i++)
{
if (obj === visited[i])
{
circular = true;
break;
}
}
if (circular)
{
json = wrapStr('[circular]');
} else if (isStr)
{
json = wrapStr(escapeJsonStr(obj));
json = wrapStr(util.escapeJsonStr(obj));
} else if (isArr)
{
visited.push(obj);
if (doStringify)
{
json = '[';
@@ -111,8 +120,6 @@ export default function getAbstract(obj, {
}
} else if (isObj)
{
visited.push(obj);
if (canBeProto(obj))
{
obj = Object.getPrototypeOf(obj);
@@ -123,29 +130,7 @@ export default function getAbstract(obj, {
{
i = 1;
json = '{ ';
util.each(names, name =>
{
if (i > keyNum)
{
objEllipsis = '...';
return;
}
let key = wrapKey(escapeJsonStr(name));
if (!getterVal)
{
let descriptor = Object.getOwnPropertyDescriptor(obj, name);
if (descriptor.get)
{
parts.push(`${key}: ${wrapStr('(...)')}`);
i++;
return;
}
}
if (typeof topObj[name] === 'function') return;
parts.push(`${key}: ${getAbstract(topObj[name], passOpts)}`);
i++;
});
util.each(names, objIteratee);
json += parts.join(', ') + objEllipsis + ' }';
} else
{
@@ -185,7 +170,6 @@ export default function getAbstract(obj, {
} else {
try
{
visited.push(obj);
if (canBeProto(obj))
{
obj = Object.getPrototypeOf(obj);
@@ -196,29 +180,7 @@ export default function getAbstract(obj, {
i = 1;
json = '{ ';
names = unenumerable ? Object.getOwnPropertyNames(obj) : Object.keys(obj);
util.each(names, name =>
{
if (i > keyNum)
{
objEllipsis = '...';
return;
}
let key = wrapKey(escapeJsonStr(name));
if (!getterVal)
{
let descriptor = Object.getOwnPropertyDescriptor(obj, name);
if (descriptor.get)
{
parts.push(`${key}: ${wrapStr('(...)')}`);
i++;
return;
}
}
if (typeof topObj[name] === 'function') return;
parts.push(`${key}: ${getAbstract(topObj[name], passOpts)}`);
i++;
});
util.each(names, objIteratee);
json += parts.join(', ') + objEllipsis + ' }';
} else
{
@@ -235,10 +197,6 @@ export default function getAbstract(obj, {
const SPECIAL_VAL = ['(...)', 'undefined', 'Symbol', 'Object'];
var escapeJsonStr = str => str.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\f|\n|\r|\t/g, '');
function canBeProto(obj)
{
let emptyObj = util.isEmpty(Object.getOwnPropertyNames(obj)),

View File

@@ -2,11 +2,11 @@ import util from './util'
// Modified from: https://jsconsole.com/
export default function stringify(obj, {
visited = [],
visitor = new Visitor(),
topObj,
level = 0,
circularMarker = false,
getterVal = false,
specialVal = false,
unenumerable = true
} = {})
{
@@ -15,67 +15,19 @@ export default function stringify(obj, {
parts = [],
names = [],
proto,
circularId,
objAbstract,
circularObj,
allKeys, keys,
id = '',
circular = false;
id = '';
topObj = topObj || obj;
let passOpts = {
visited, specialVal, getterVal,
unenumerable,
level: level + 1
},
passProtoOpts = {
visited, specialVal, getterVal, topObj,
unenumerable,
level: level + 1
};
let passOpts = {visitor, getterVal, unenumerable, level: level + 1},
passProtoOpts = {visitor, getterVal, topObj, unenumerable, level: level + 1};
let specialWrapper = '',
fnWrapper = '',
strEscape = str => str,
wrapperEnd = '';
let wrapKey = key => `"${key}"`,
wrapStr = str => `"${util.toStr(str)}"`;
let wrapKey = key => '"' + strEscape(key) + '"';
function visit(value)
{
let id = util.uniqId('erudaJson');
visited.push({id, value});
return id;
}
function wrapStr(str)
{
str = util.toStr(str);
if (specialVal)
{
str = str.replace(/\\/g, '');
if (util.startWith(str, 'function'))
{
return fnWrapper + 'function' + wrapperEnd + ' ( )';
}
if (util.contain(SPECIAL_VAL, str) || util.startWith(str, 'Array['))
{
return specialWrapper + strEscape(str) + wrapperEnd;
}
if (util.startWith(str, '[circular]'))
{
return specialWrapper + '[circular]' + wrapperEnd;
}
if (util.startWith(str, '[object '))
{
return specialWrapper + strEscape(str.replace(/(\[object )|]/g, '')) + wrapperEnd;
}
}
return strEscape(`"${str}"`);
}
try {
type = ({}).toString.call(obj);
} catch (e)
@@ -91,25 +43,18 @@ export default function stringify(obj, {
isSymbol = (type == '[object Symbol]'),
isBool = (type == '[object Boolean]');
for (let i = 0, len = visited.length; i < len; i++)
{
if (obj === visited[i].value)
{
circular = true;
circularId = visited[i].id;
break;
}
}
circularObj = visitor.check(obj);
if (circular)
if (circularObj)
{
json = wrapStr('[circular]' + ' ' + circularId);
json = stringify(circularObj.abstract, {circularMarker: true});
} else if (isStr)
{
json = wrapStr(escapeJsonStr(obj));
json = wrapStr(util.escapeJsonStr(obj));
} else if (isArr)
{
id = visit(obj);
id = visitor.visit(obj);
visitor.updateAbstract(id, [`erudaCircular ${id}`]);
json = '[';
util.each(obj, val => parts.push(`${stringify(val, passOpts)}`));
@@ -117,19 +62,23 @@ export default function stringify(obj, {
json += parts.join(', ') + ']';
} else if (isObj || isFn)
{
id = visit(obj);
id = visitor.visit(obj);
if (canBeProto(obj))
{
obj = Object.getPrototypeOf(obj);
id = visit(obj);
id = visitor.visit(obj);
}
allKeys = Object.getOwnPropertyNames(obj);
keys = Object.keys(obj);
names = unenumerable ? allKeys : keys;
proto = Object.getPrototypeOf(obj);
if (proto) proto = `${wrapKey('erudaProto')}: ${stringify(proto, passProtoOpts)}`;
if (circularMarker && proto === Object.prototype) proto = null;
if (proto)
{
proto = `${wrapKey('erudaProto')}: ${stringify(proto, passProtoOpts)}`;
}
names.sort(sortObjName);
if (isFn)
{
@@ -137,29 +86,14 @@ export default function stringify(obj, {
names = names.filter(val => ['arguments', 'caller', 'prototype'].indexOf(val) < 0);
}
json = '{ ';
if (isFn) parts.push(`${wrapKey('erudaObjAbstract')}: ${wrapStr(getFnAbstract(obj))}`);
parts.push(`"erudaId": "${id}"`);
util.each(names, name =>
{
let unenumerable = !util.contain(keys, name) ? 'erudaUnenumerable' : '',
key = wrapKey(unenumerable + ' ' + escapeJsonStr(name)),
getKey = wrapKey(unenumerable + ' ' + escapeJsonStr('get ' + name)),
setKey = wrapKey(unenumerable + ' ' + escapeJsonStr('set ' + name));
let descriptor = Object.getOwnPropertyDescriptor(obj, name),
hasGetter = descriptor && descriptor.get,
hasSetter = descriptor && descriptor.set;
if (!getterVal && hasGetter)
{
parts.push(`${getKey}: ${wrapStr(escapeJsonStr(extractFnHead(descriptor.get)))}`);
}
if (hasSetter)
{
parts.push(`${setKey}: ${wrapStr(escapeJsonStr(extractFnHead(descriptor.set)))}`);
}
if (!getterVal && hasGetter) return;
parts.push(`${key}: ${stringify(topObj[name], passOpts)}`);
objAbstract = isFn ? getFnAbstract(obj) : type.replace(/(\[object )|]/g, '');
visitor.updateAbstract(id, {
erudaObjAbstract: objAbstract,
erudaCircular: id
});
parts.push(`${wrapKey('erudaObjAbstract')}: ${wrapStr(objAbstract)}`);
if (!circularMarker) parts.push(`"erudaId": "${id}"`);
util.each(names, objIteratee);
if (proto) parts.push(proto);
json += parts.join(', ') + ' }';
} else if (isNum)
@@ -193,20 +127,26 @@ export default function stringify(obj, {
} else {
try
{
id = visit(obj);
id = visitor.visit(obj);
if (canBeProto(obj))
{
obj = Object.getPrototypeOf(obj);
id = visit(obj);
id = visitor.visit(obj);
}
json = '{ ';
parts.push(`${wrapKey('erudaObjAbstract')}: "${type.replace(/(\[object )|]/g, '')}"`);
parts.push(`"erudaId": "${id}"`);
objAbstract = type.replace(/(\[object )|]/g, '');
visitor.updateAbstract(id, {
erudaObjAbstract: objAbstract,
erudaCircular: id
});
parts.push(`${wrapKey('erudaObjAbstract')}: ${wrapStr(objAbstract)}`);
if (!circularMarker) parts.push(`"erudaId": "${id}"`);
allKeys = Object.getOwnPropertyNames(obj);
keys = Object.keys(obj);
names = unenumerable ? allKeys : keys;
proto = Object.getPrototypeOf(obj);
if (circularMarker && proto === Object.prototype) proto = null;
if (proto)
{
try
@@ -214,49 +154,46 @@ export default function stringify(obj, {
proto = `${wrapKey('erudaProto')}: ${stringify(proto, passProtoOpts)}`;
} catch(e)
{
proto = `${wrapKey('erudaProto')}: ${wrapStr(escapeJsonStr(e.message))}`;
proto = `${wrapKey('erudaProto')}: ${wrapStr(e.message)}`;
}
}
names.sort(sortObjName);
util.each(names, name =>
{
let unenumerable = !util.contain(keys, name) ? 'erudaUnenumerable' : '',
key = wrapKey(unenumerable + ' ' + escapeJsonStr(name)),
getKey = wrapKey(unenumerable + ' ' + escapeJsonStr('get ' + name)),
setKey = wrapKey(unenumerable + ' ' + escapeJsonStr('set ' + name));
let descriptor = Object.getOwnPropertyDescriptor(obj, name),
hasGetter = descriptor && descriptor.get,
hasSetter = descriptor && descriptor.set;
if (!getterVal && hasGetter)
{
parts.push(`${getKey}: ${wrapStr(escapeJsonStr(extractFnHead(descriptor.get)))}`);
}
if (hasSetter)
{
parts.push(`${setKey}: ${wrapStr(escapeJsonStr(extractFnHead(descriptor.set)))}`);
}
if (!getterVal && hasGetter) return;
parts.push(`${key}: ${stringify(topObj[name], passOpts)}`);
});
util.each(names, objIteratee);
if (proto) parts.push(proto);
json += parts.join(', ') + ' }';
} catch (e)
{
json = wrapStr(e.message);
// json = wrapStr(obj);
json = wrapStr(obj);
}
}
function objIteratee(name)
{
let unenumerable = !util.contain(keys, name) ? 'erudaUnenumerable ' : '',
key = wrapKey(unenumerable + util.escapeJsonStr(name)),
getKey = wrapKey(unenumerable + util.escapeJsonStr('get ' + name)),
setKey = wrapKey(unenumerable + util.escapeJsonStr('set ' + name));
let descriptor = Object.getOwnPropertyDescriptor(obj, name),
hasGetter = descriptor && descriptor.get,
hasSetter = descriptor && descriptor.set;
if (!getterVal && hasGetter)
{
parts.push(`${getKey}: ${wrapStr(util.escapeJsonStr(extractFnHead(descriptor.get)))}`);
} else
{
parts.push(`${key}: ${stringify(topObj[name], passOpts)}`);
}
if (hasSetter)
{
parts.push(`${setKey}: ${wrapStr(util.escapeJsonStr(extractFnHead(descriptor.set)))}`);
}
}
return json;
}
const SPECIAL_VAL = ['(...)', 'undefined', 'Symbol', 'Object'];
var escapeJsonStr = str => str.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\f|\n|\r|\t/g, '');
// $, upperCase, lowerCase, _
var sortObjName = (a, b) =>
{
@@ -311,7 +248,7 @@ function getFnAbstract(fn)
let fnStr = fn.toString();
if (fnStr.length > 500) fnStr = fnStr.slice(0, 500) + '...';
return escapeJsonStr(extractFnHead(fnStr).replace('function', ''));
return util.escapeJsonStr(extractFnHead(fnStr).replace('function', ''));
}
function canBeProto(obj)
@@ -322,3 +259,39 @@ function canBeProto(obj)
return emptyObj && proto && proto !== Object.prototype;
}
class Visitor
{
constructor()
{
this._visited = [];
this._map = {};
}
visit(val)
{
let id = util.uniqId('erudaJson');
this._visited.push({id, val, abstract: {}});
this._map[id] = util.last(this._visited);
return id;
}
check(val)
{
let visited = this._visited;
for (let i = 0, len = visited.length; i < len; i++)
{
if (val === visited[i].val) return visited[i];
}
return false;
}
update(id, data)
{
util.extend(this._map[id], data);
}
updateAbstract(id, abstract)
{
this.update(id, {abstract});
}
}

View File

@@ -506,6 +506,20 @@ module.exports = (function ()
return exports;
})();
/* ------------------------------ escapeJsonStr ------------------------------ */
var escapeJsonStr = _.escapeJsonStr = (function ()
{
function exports(str)
{
return str.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\f|\n|\r|\t/g, '');
}
return exports;
})();
/* ------------------------------ escapeRegExp ------------------------------ */
var escapeRegExp = _.escapeRegExp = (function ()