JSON.stringify 深层对象
Posted
技术标签:
【中文标题】JSON.stringify 深层对象【英文标题】:JSON.stringify deep objects 【发布时间】:2012-12-01 10:13:23 【问题描述】:我需要一个从任何参数构建 JSON 有效字符串的函数,但是:
通过不添加两次对象来避免递归问题 通过截断超过给定深度来避免调用堆栈大小问题通常它应该能够处理大对象,但会截断它们。
作为参考,此代码失败:
var json = JSON.stringify(window);
避免递归问题很简单:
var seen = [];
return JSON.stringify(o, function(_, value)
if (typeof value === 'object' && value !== null)
if (seen.indexOf(value) !== -1) return;
else seen.push(value);
return value;
);
但是现在,除了复制和更改Douglas Crockford's code 以跟踪深度之外,我没有找到任何方法来避免在非常深的对象(如window
或任何event
)上发生堆栈溢出。有没有简单的解决办法?
【问题讨论】:
“非常深的对象”是什么意思?真的存在超出堆栈大小的对象(没有“递归属性”)吗? 是的:例如window
。我的代码中可能存在错误,但真正的问题是递归性,因为window
既是递归的又是深度的(这就是我给出代码的原因)。
嗯,在 window
上尝试您的脚本时,我收到了 out of (heap) memory
:-/
这就是我想要避免的。 try/catch 在我的测试中阻止了它,但我什么都没有。我希望至少有前 N 个级别。如有必要,我也会削减大数组/字符串,但这很简单。
你的实现依赖于原生的JSON.stringify
。而且我担心在“属性树”中执行 DFS,而不是 BFS。如果您可以逐级扩展可能会变得更好(虽然不确定 - 结果只是 is 一个太大的字符串)
【参考方案1】:
我认为您使用的格式不适合做您想做的事。将包含在窗口对象中的所有数据转换为单个 JSON 字符串假设您在构建她的过程中将此字符串保留在内存中,从而导致您遇到的问题。
您需要一种格式,让您能够在从窗口对象解析数据时发送数据,以便动态释放内存。就此而言,您应该使用 CSV、Text 或 VarStream (https://github.com/nfroidure/VarStream) 之类的内容。
您还可以遍历对象并尝试 JSON.stringify 它们在 try ... catch 中。如果尝试成功,则发送 JSON 文件,如果失败,则使用相同的 try ... catch 等遍历对象属性...但这是一个丑陋的解决方法,我不鼓励您使用。
【讨论】:
【参考方案2】:你可以保持你所处的深度:
function stringify(obj, currentDepth, maxDepth)
if (currentDepth == maxDepth) return '[Warning: max level reached]'
var str = '';
for (var key in obj)
str += key + ': ' + typeof obj == 'object' ?
stringify(obj[key], currentDepth + 1, maxDepth) :
obj[key];
return str + ''
(只是示例——显然这个 sn-p 没有检测到递归)
【讨论】:
这不会从任何对象构建 JSON 字符串。【参考方案3】:我做了我最初担心我必须做的事情:我采用了 Crockford 的代码并根据我的需要对其进行了修改。现在它构建 JSON 但处理
循环 对象太深 数组太长 例外(无法合法访问的访问器)如果有人需要,我创建了一个 GitHub 存储库:JSON.prune on GitHub
代码如下:
// JSON.pruned : a function to stringify any object without overflow
// example : var json = JSON.pruned(a:'e', c:[1,2,d:e:42, f:'deep'])
// two additional optional parameters :
// - the maximal depth (default : 6)
// - the maximal length of arrays (default : 50)
// GitHub : https://github.com/Canop/JSON.prune
// This is based on Douglas Crockford's code ( https://github.com/douglascrockford/JSON-js/blob/master/json2.js )
(function ()
'use strict';
var DEFAULT_MAX_DEPTH = 6;
var DEFAULT_ARRAY_MAX_LENGTH = 50;
var seen; // Same variable used for all stringifications
Date.prototype.toPrunedJSON = Date.prototype.toJSON;
String.prototype.toPrunedJSON = String.prototype.toJSON;
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
meta = // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
;
function quote(string)
escapable.lastIndex = 0;
return escapable.test(string) ? '"' + string.replace(escapable, function (a)
var c = meta[a];
return typeof c === 'string'
? c
: '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
) + '"' : '"' + string + '"';
function str(key, holder, depthDecr, arrayMaxLength)
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
partial,
value = holder[key];
if (value && typeof value === 'object' && typeof value.toPrunedJSON === 'function')
value = value.toPrunedJSON(key);
switch (typeof value)
case 'string':
return quote(value);
case 'number':
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
return String(value);
case 'object':
if (!value)
return 'null';
if (depthDecr<=0 || seen.indexOf(value)!==-1)
return '"-pruned-"';
seen.push(value);
partial = [];
if (Object.prototype.toString.apply(value) === '[object Array]')
length = Math.min(value.length, arrayMaxLength);
for (i = 0; i < length; i += 1)
partial[i] = str(i, value, depthDecr-1, arrayMaxLength) || 'null';
v = partial.length === 0
? '[]'
: '[' + partial.join(',') + ']';
return v;
for (k in value)
if (Object.prototype.hasOwnProperty.call(value, k))
try
v = str(k, value, depthDecr-1, arrayMaxLength);
if (v) partial.push(quote(k) + ':' + v);
catch (e)
// this try/catch due to some "Accessing selectionEnd on an input element that cannot have a selection." on Chrome
v = partial.length === 0
? ''
: '' + partial.join(',') + '';
return v;
JSON.pruned = function (value, depthDecr, arrayMaxLength)
seen = [];
depthDecr = depthDecr || DEFAULT_MAX_DEPTH;
arrayMaxLength = arrayMaxLength || DEFAULT_ARRAY_MAX_LENGTH;
return str('', '': value, depthDecr, arrayMaxLength);
;
());
可以做的一个例子:
var json = JSON.pruned(window);
注意:与此答案中的代码相反,GitHub repository 在需要时更新(文档、兼容性、在 commonjs 或节点中用作模块、特定序列化等)。如果您需要此修剪功能,最好从存储库开始。
【讨论】:
@SarahManning JSON 当然不包含函数。如果你想序列化它们,你可以用 JSON.prune 来做:github.com/Canop/JSON.prune#example-4-function-serialization 任何需要功能的人看到这个问题github.com/Canop/JSON.prune/issues/5 你在这里创造了历史,伙计【参考方案4】:您可以简单地使用Censor 函数,如下例所示:
function censor(key, value)
if (typeof(value) == "string")
return undefined;
return value;
var foo = foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7;
var jsonString = JSON.stringify(foo, censor);
输出是"week":45,"month":7
。
所以对于你的例子,如果你有一个值对象,你必须返回 undefined,它是一个窗口。
【讨论】:
【参考方案5】:我已经修改了@dystroy 的答案,添加:
子属性的缩进。 循环引用指向的指示。/**
* Returns the JSON representation of an object.
*
* @param value object the object
* @param number objectMaxDepth for objects, the maximum number of times to recurse into descendants
* @param number arrayMaxLength for arrays, the maximum number of elements to enumerate
* @param string indent the string to use for indentation
* @return string the JSON representation
*/
var toJSON = function(object, objectMaxDepth, arrayMaxLength, indent)
"use strict";
/**
* Escapes control characters, quote characters, backslash characters and quotes the string.
*
* @param string string the string to quote
* @returns String the quoted string
*/
function quote(string)
escapable.lastIndex = 0;
var escaped;
if (escapable.test(string))
escaped = string.replace(escapable, function(a)
var replacement = replacements[a];
if (typeof (replacement) === "string")
return replacement;
// Pad the unicode representation with leading zeros, up to 4 characters.
return "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4);
);
else
escaped = string;
return "\"" + escaped + "\"";
/**
* Returns the String representation of an object.
*
* Based on <a href="https://github.com/Canop/JSON.prune/blob/master/JSON.prune.js">https://github.com/Canop/JSON.prune/blob/master/JSON.prune.js</a>
*
* @param string path the fully-qualified path of value in the JSON object
* @param type value the value of the property
* @param string cumulativeIndent the indentation to apply at this level
* @param number depth the current recursion depth
* @return String the JSON representation of the object, or "null" for values that aren't valid
* in JSON (e.g. infinite numbers).
*/
function toString(path, value, cumulativeIndent, depth)
switch (typeof (value))
case "string":
return quote(value);
case "number":
// JSON numbers must be finite
if (isFinite(value))
return String(value);
return "null";
case "boolean":
return String(value);
case "object":
if (!value)
return "null";
var valueIndex = values.indexOf(value);
if (valueIndex !== -1)
return "Reference => " + paths[valueIndex];
values.push(value);
paths.push(path);
if (depth > objectMaxDepth)
return "...";
// Make an array to hold the partial results of stringifying this object value.
var partial = [];
// Is the value an array?
var i;
if (Object.prototype.toString.apply(value) === "[object Array]")
// The value is an array. Stringify every element
var length = Math.min(value.length, arrayMaxLength);
// Whether a property has one or multiple values, they should be treated as the same
// object depth. As such, we do not increment the object depth when recursing into an
// array.
for (i = 0; i < length; ++i)
partial[i] = toString(path + "." + i, value[i], cumulativeIndent + indent, depth,
arrayMaxLength);
if (i < value.length)
// arrayMaxLength reached
partial[i] = "...";
return "\n" + cumulativeIndent + "[" + partial.join(", ") + "\n" + cumulativeIndent +
"]";
// Otherwise, iterate through all of the keys in the object.
for (var subKey in value)
if (Object.prototype.hasOwnProperty.call(value, subKey))
var subValue;
try
subValue = toString(path + "." + subKey, value[subKey], cumulativeIndent + indent,
depth + 1);
partial.push(quote(subKey) + ": " + subValue);
catch (e)
// this try/catch due to forbidden accessors on some objects
if (e.message)
subKey = e.message;
else
subKey = "access denied";
var result = "\n" + cumulativeIndent + "\n";
for (i = 0; i < partial.length; ++i)
result += cumulativeIndent + indent + partial[i] + ",\n";
if (partial.length > 0)
// Remove trailing comma
result = result.slice(0, result.length - 2) + "\n";
result += cumulativeIndent + "";
return result;
default:
return "null";
if (indent === undefined)
indent = " ";
if (objectMaxDepth === undefined)
objectMaxDepth = 0;
if (arrayMaxLength === undefined)
arrayMaxLength = 50;
// Matches characters that must be escaped
var escapable =
/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
// The replacement characters
var replacements =
"\b": "\\b",
"\t": "\\t",
"\n": "\\n",
"\f": "\\f",
"\r": "\\r",
"\"": "\\\"",
"\\": "\\\\"
;
// A list of all the objects that were seen (used to avoid recursion)
var values = [];
// The path of an object in the JSON object, with indexes corresponding to entries in the
// "values" variable.
var paths = [];
return toString("root", object, "", 0);
;
【讨论】:
【参考方案6】:这是我的字符串化器,用于剥离 JSON 以安全记录具有循环引用、DOM 元素、角度范围或窗口的对象。
通过将循环引用替换为 '' 来防止 TypeError: Converting circular structure to JSON
。
阻止RangeError: Maximum call stack size exceeded
。
但是,无论如何还是建议使用 maxDepth 或 filterObjects,因为序列化非常深的对象会耗费时间和空间,这可能会降低其对一般日志记录的可用性,甚至在用于测试时会导致测试浏览器断开连接。
可选:
限制对象检查深度(尚未实现), 过滤对象(如窗口、测试框架、测试运行器), 过滤 DOM 元素, 过滤角度对象$attributes。来源+cmets:https://gist.github.com/iki/9371373
【讨论】:
【参考方案7】:如果您使用的是 Node.js,则可以使用 util.inspect
,它接受深度参数。
【讨论】:
16.5.0 的文档表明它仍然受支持。 nodejs.org/api/…【参考方案8】:(function (input, level)
if (!input)
return input;
level = level || 4;
var objectsAlreadySerialized = [input],
objDepth = [input];
return JSON.stringify(input, function (key, value)
if (key)
if (typeof value === 'object')
if (objectsAlreadySerialized.indexOf(value) !== -1)
return undefined;
objectsAlreadySerialized.push(value);
if (objDepth.indexOf(this) === -1)
objDepth.push(this);
else while(objDepth[objDepth.length-1] !== this)
objDepth.pop();
if (objDepth.length > level)
return undefined;
return value;
);
)(window, 6)
【讨论】:
虽然此代码可能会回答问题,但提供有关它如何和/或为什么解决问题的额外上下文将提高答案的长期价值。【参考方案9】:这是一个尊重内置 JSON.stringify() 规则同时也限制深度的函数。此版本通过将循环引用设为 null 或使用可选回调来获取对象 ID(例如 GUID)来处理循环引用。
function stringify(val, depth, replacer, space, onGetObjID)
depth = isNaN(+depth) ? 1 : depth;
var recursMap = new WeakMap();
function _build(val, depth, o, a, r) // (JSON.stringify() has it's own rules, which we respect here by using it for property iteration)
return !val || typeof val != 'object' ? val
: (r = recursMap.has(val), recursMap.set(val,true), a = Array.isArray(val),
r ? (o=onGetObjID&&onGetObjID(val)||null) : JSON.stringify(val, function(k,v) if (a || depth > 0) if (replacer) v=replacer(k,v); if (!k) return (a=Array.isArray(v),val=v); !o && (o=a?[]:); o[k] = _build(v, a?depth:depth-1); ),
o===void 0 ? (a?[]:) : o);
return JSON.stringify(_build(val, depth), null, space);
var o = id:'SOMEGUID',t:true;
var value=a:[12,2,y:3,z:o1:o],s:'!',b:x:1,o2:o,o3:o;
console.log(stringify(value, 0, (k,v)=>console.log('key:'+k+';val:',v); return v, 2));
console.log(stringify(value, 1, (k,v)=>console.log('key:'+k+';val:',v); return v, 2));
console.log(stringify(value, 2, (k,v)=>console.log('key:'+k+';val:',v); return v, 2));
console.log(stringify(value, 3, (k,v)=>console.log('key:'+k+';val:',v); return v, 2));
console.log(stringify(value, 4, (k,v)=>console.log('key:'+k+';val:',v); return v, 2, (v)=>return v.id));
"a": [
12,
2,
],
"s": "!",
"b":
"a": [
12,
2,
"y": 3,
"z":
],
"s": "!",
"b":
"x": 1,
"o2": ,
"o3": null
"a": [
12,
2,
"y": 3,
"z":
"o1":
],
"s": "!",
"b":
"x": 1,
"o2": null,
"o3": null
"a": [
12,
2,
"y": 3,
"z":
"o1":
"id": "SOMEGUID",
"t": true
],
"s": "!",
"b":
"x": 1,
"o2": "SOMEGUID",
"o3": "SOMEGUID"
(取自我在这里的帖子https://***.com/a/57193068/1236397)
这是一个 TypeScript 版本:
/** A more powerful version of the built-in JSON.stringify() function that uses the same function to respect the
* built-in rules while also limiting depth and supporting cyclical references.
*/
export function stringify(val: any, depth: number, replacer: (this: any, key: string, value: any) => any, space?: string | number, onGetObjID?: (val: object) => string): string
depth = isNaN(+depth) ? 1 : depth;
var recursMap = new WeakMap();
function _build(val: any, depth: number, o?: any, a?: boolean, r?: boolean)
return !val || typeof val != 'object' ? val
: (r = recursMap.has(val),
recursMap.set(val, true),
a = Array.isArray(val),
r ? (o = onGetObjID && onGetObjID(val) || null) : JSON.stringify(val, function (k, v) if (a || depth > 0) if (replacer) v = replacer(k, v); if (!k) return (a = Array.isArray(v), val = v); !o && (o = a ? [] : ); o[k] = _build(v, a ? depth : depth - 1); ),
o === void 0 ? (a?[]:) : o);
return JSON.stringify(_build(val, depth), null, space);
注意: 数组被视为字符串 - 原始值数组;因此,任何嵌套的对象项都被视为下一级而不是数组对象本身(很像字符串可以是字符数组,但它是一个实体)。
更新:修复了空数组呈现为空对象的错误。
【讨论】:
以上是关于JSON.stringify 深层对象的主要内容,如果未能解决你的问题,请参考以下文章