JSON.stringify,避免TypeError:将圆形结构转换为JSON

我有一个大对象,我想转换为JSON并发送。 不过它有圆形结构。 我想扔什么循环引用存在,并发送任何可以被串行化。 我怎么做?

谢谢。

var obj = { a: "foo", b: obj } 

我想把obj变成:

 {"a":"foo"} 

使用JSON.stringify与自定义JSON.stringify 。 例如:

 // Demo: Circular reference var o = {}; oo = o; // Note: cache should not be re-used by repeated calls to JSON.stringify. var cache = []; JSON.stringify(o, function(key, value) { if (typeof value === 'object' && value !== null) { if (cache.indexOf(value) !== -1) { // Circular reference found, discard key return; } // Store value in our collection cache.push(value); } return value; }); cache = null; // Enable garbage collection 

这个例子中的替代者不是100%正确的(取决于你对“重复”的定义)。 在以下情况下,将丢弃一个值:

 var a = {b:1} var o = {}; o.one = a; o.two = a; // one and two point to the same object, but two is discarded: JSON.stringify(o, ...); 

但是这个概念的含义是:使用一个自定义replace器,并跟踪parsing的对象值。

在Node.js中,可以使用util.inspect(object) 。 它会自动用“[Circular]”replace循环链接。

我真的很喜欢Trindaz的解决scheme – 更详细,但它有一些错误。 我固定他们谁也喜欢它。

另外,我在caching对象上添加了一个长度限制。

如果我打印的对象真的很大 – 我的意思是无限大 – 我想限制我的algorithm。

 JSON.stringifyOnce = function(obj, replacer, indent){ var printedObjects = []; var printedObjectKeys = []; function printOnceReplacer(key, value){ if ( printedObjects.length > 2000){ // browsers will not print more than 20K, I don't see the point to allow 2K.. algorithm will not be fast anyway if we have too many objects return 'object too long'; } var printedObjIndex = false; printedObjects.forEach(function(obj, index){ if(obj===value){ printedObjIndex = index; } }); if ( key == ''){ //root element printedObjects.push(obj); printedObjectKeys.push("root"); return value; } else if(printedObjIndex+"" != "false" && typeof(value)=="object"){ if ( printedObjectKeys[printedObjIndex] == "root"){ return "(pointer to root)"; }else{ return "(see " + ((!!value && !!value.constructor) ? value.constructor.name.toLowerCase() : typeof(value)) + " with key " + printedObjectKeys[printedObjIndex] + ")"; } }else{ var qualifiedKey = key || "(empty key)"; printedObjects.push(value); printedObjectKeys.push(qualifiedKey); if(replacer){ return replacer(key, value); }else{ return value; } } } return JSON.stringify(obj, printOnceReplacer, indent); }; 

请注意,还有Douglas Crockford实现的JSON.decycle方法。 看到他的cycle.js 。 这使您可以将几乎任何标准结构串联起来:

 var a = []; a[0] = a; a[1] = 123; console.log(JSON.stringify(JSON.decycle(a))); // result: '[{"$ref":"$"},123]'. 

您也可以使用retrocycle方法重新创build原始对象。 所以你不必从对象中移除循环来将它们串起来。

但是这对于DOM节点(这是现实生活中使用周期的典型原因)不起作用。 例如这将抛出:

 var a = [document.body]; console.log(JSON.stringify(JSON.decycle(a))); 

我已经做了一个fork来解决这个问题(请参阅我的cycle.js fork )。 这应该工作正常:

 var a = [document.body]; console.log(JSON.stringify(JSON.decycle(a, true))); 

我build议从@ isaacs检查json-stringify-safe -它在NPM中使用。

顺便说一句,如果你不使用Node.js,你可以从源代码的相关部分复制粘贴4-27行。

安装:

 $ npm install json-stringify-safe --save 

使用:

 // Require the thing var stringify = require('json-stringify-safe'); // Take some nasty circular object var theBigNasty = { a: "foo", b: theBigNasty }; // Then clean it up a little bit var sanitized = JSON.parse(stringify(theBigNasty)); 

这产生:

 { a: 'foo', b: '[Circular]' } 

请注意,就像使用@Rob W提到的vanilla JSON.stringify函数一样,您也可以通过传入“replacer”函数作为stringify()的第二个参数来自定义消毒行为。 如果你发现自己需要一个简单的例子来说明如何做到这一点,我就写了一个自定义replace器, 在这里把错误,正则expression式和函数强制转换为可读的string。

做就是了

 npm i --save circular-json 

然后在你的js文件中

 const JSON = require('circular-json'); ... const json = JSON.stringify(obj); 

你也可以做

 const CircularJSON = require('circular-json'); 

https://github.com/WebReflection/circular-json

注:我没有任何关系这个包。 但我确实使用它。

对于未知的googlers,如果您知道所有循环引用的键,则可以使用JSON.stringify函数的包装来排除循环引用。 请参阅https://gist.github.com/4653128上的示例脚本。;

解决scheme实质上归结为保持对数组中先前打印的对象的引用,并在返回值之前在replace函数中检查该对象。 这比仅仅排除循环引用更紧缩,因为它也排除了打印一个对象两次,其中一个副作用是避免循环引用。

示例包装:

 function stringifyOnce(obj, replacer, indent){ var printedObjects = []; var printedObjectKeys = []; function printOnceReplacer(key, value){ var printedObjIndex = false; printedObjects.forEach(function(obj, index){ if(obj===value){ printedObjIndex = index; } }); if(printedObjIndex && typeof(value)=="object"){ return "(see " + value.constructor.name.toLowerCase() + " with key " + printedObjectKeys[printedObjIndex] + ")"; }else{ var qualifiedKey = key || "(empty key)"; printedObjects.push(value); printedObjectKeys.push(qualifiedKey); if(replacer){ return replacer(key, value); }else{ return value; } } } return JSON.stringify(obj, printOnceReplacer, indent); } 

与替代品一起使用JSON.stringify方法。 阅读此文档以获取更多信息。 http://msdn.microsoft.com/en-us/library/cc836459%28v=vs.94%29.aspx

 var obj = { a: "foo", b: obj } var replacement = {"b":undefined}; alert(JSON.stringify(obj,replacement)); 

找出用循环引用填充replace数组的方法。 您可以使用typeof方法来查找属性的types是否为“对象”(引用),并使用精确的相等性检查(===)来validation循环引用。

 var a={b:"b"}; aa=a; JSON.stringify(preventCircularJson(a)); 

评估为:

 "{"b":"b","a":"CIRCULAR_REFERENCE_REMOVED"}" 

具有以下function:

 /** * Traverses a javascript object, and deletes all circular values * @param source object to remove circular references from * @param censoredMessage optional: what to put instead of censored values * @param censorTheseItems should be kept null, used in recursion * @returns {undefined} */ function preventCircularJson(source, censoredMessage, censorTheseItems) { //init recursive value if this is the first call censorTheseItems = censorTheseItems || [source]; //default if none is specified censoredMessage = censoredMessage || "CIRCULAR_REFERENCE_REMOVED"; //values that have allready apeared will be placed here: var recursiveItems = {}; //initaite a censored clone to return back var ret = {}; //traverse the object: for (var key in source) { var value = source[key] if (typeof value == "object") { //re-examine all complex children again later: recursiveItems[key] = value; } else { //simple values copied as is ret[key] = value; } } //create list of values to censor: var censorChildItems = []; for (var key in recursiveItems) { var value = source[key]; //all complex child objects should not apear again in children: censorChildItems.push(value); } //censor all circular values for (var key in recursiveItems) { var value = source[key]; var censored = false; censorTheseItems.forEach(function (item) { if (item === value) { censored = true; } }); if (censored) { //change circular values to this value = censoredMessage; } else { //recursion: value = preventCircularJson(value, censoredMessage, censorChildItems.concat(censorTheseItems)); } ret[key] = value } return ret; } 

我知道这是一个古老的问题,但我想build议一个我创build的称为智能通告的NPM包,其工作方式与提出的其他方式不同。 如果您使用大而深的对象,这是特别有用的。

一些function是:

  • 通过导致第一次出现的path(而不仅仅是string[circular] )replace循环引用或简单地重复对象内部的结构;

  • 通过在广度优先search中查找循环,包确保这条path尽可能小,这对处理非常大而深的对象很重要,这些对象的path可能会变得烦人而且很难跟踪(自定义replaceJSON.stringify做一个DFS);

  • 允许个性化replace,方便简化或忽略不太重要的对象部分;

  • 最后,path将按照访问所引用字段的方式完全写入,这可以帮助您进行debugging。

我解决这个问题是这样的:

 var util = require('util'); // Our circular object var obj = {foo: {bar: null}, a:{a:{a:{a:{a:{a:{a:{hi: 'Yo!'}}}}}}}}; obj.foo.bar = obj; // Generate almost valid JS object definition code (typeof string) var str = util.inspect(b, {depth: null}); // Fix code to the valid state (in this example it is not required, but my object was huge and complex, and I needed this for my case) str = str .replace(/<Buffer[ \w\.]+>/ig, '"buffer"') .replace(/\[Function]/ig, 'function(){}') .replace(/\[Circular]/ig, '"Circular"') .replace(/\{ \[Function: ([\w]+)]/ig, '{ $1: function $1 () {},') .replace(/\[Function: ([\w]+)]/ig, 'function $1(){}') .replace(/(\w+): ([\w :]+GMT\+[\w \(\)]+),/ig, '$1: new Date("$2"),') .replace(/(\S+): ,/ig, '$1: null,'); // Create function to eval stringifyed code var foo = new Function('return ' + str + ';'); // And have fun console.log(JSON.stringify(foo(), null, 4)); 

我知道这个问题是旧的,有很多很好的答案,但我发布这个答案,因为它的新味道(ES5 +)

 Object.defineProperties(JSON, { refStringify: { value: function(obj) { let objMap = new Map(); let stringified = JSON.stringify(obj, function(key, value) { // only for objects if (typeof value == 'object') { // If has the value then return a reference to it if (objMap.has(value)) return objMap.get(value); objMap.set(value, `ref${objMap.size + 1}`); } return value; }); return stringified; } }, refParse: { value: function(str) { let parsed = JSON.parse(str); let objMap = _createObjectMap(parsed); objMap.forEach((value, key) => _replaceKeyWithObject(value, key)); return parsed; } }, }); // *************************** Example let a = { b: 32, c: { get a() { return a; }, get c() { return ac; } } }; let stringified = JSON.refStringify(a); let parsed = JSON.refParse(stringified, 2); console.log(parsed, JSON.refStringify(parsed)); // *************************** /Example // *************************** Helper function _createObjectMap(obj) { let objMap = new Map(); JSON.stringify(obj, (key, value) => { if (typeof value == 'object') { if (objMap.has(value)) return objMap.get(value); objMap.set(value, `ref${objMap.size + 1}`); } return value; }); return objMap; } function _replaceKeyWithObject(key, obj, replaceWithObject = obj) { Object.keys(obj).forEach(k => { let val = obj[k]; if (val == key) return (obj[k] = replaceWithObject); if (typeof val == 'object' && val != replaceWithObject) _replaceKeyWithObject(key, val, replaceWithObject); }); } 

用这种types的对象解决这个问题的另一个解决scheme是使用这个库

https://github.com/ericmuyser/stringy

它的简单,你可以在几个简单的步骤来解决这个问题。

我在github上find了circular-json库 ,它对我的​​问题很好。

我发现有用的一些很好的function:

  • 支持多平台使用,但目前为止我只用node.js进行testing。
  • API是相同的,所以你需要做的就是包含并用它作为JSONreplace。
  • 它有它自己的parsing方法,所以你可以将“循环”序列化数据转换回对象。

基于其他的答案我结束了以下代码。 它适用于循环引用,具有自定义构造函数的对象。

从给定对象序列化,

  • caching遍历对象时遇到的所有对象,并为它们分配一个唯一的hashID(自动递增的数字也起作用)
  • 一旦find循环引用,将新对象中的该字段标记为循环,并将原始对象的hashID作为属性存储。

Github链接 – DecycledJSON

 DJSHelper = {}; DJSHelper.Cache = []; DJSHelper.currentHashID = 0; DJSHelper.ReviveCache = []; // DOES NOT SERIALIZE FUNCTION function DJSNode(name, object, isRoot){ this.name = name; // [ATTRIBUTES] contains the primitive fields of the Node this.attributes = {}; // [CHILDREN] contains the Object/Typed fields of the Node // All [CHILDREN] must be of type [DJSNode] this.children = []; //Array of DJSNodes only // If [IS-ROOT] is true reset the Cache and currentHashId // before encoding isRoot = typeof isRoot === 'undefined'? true:isRoot; this.isRoot = isRoot; if(isRoot){ DJSHelper.Cache = []; DJSHelper.currentHashID = 0; // CACHE THE ROOT object.hashID = DJSHelper.currentHashID++; DJSHelper.Cache.push(object); } for(var a in object){ if(object.hasOwnProperty(a)){ var val = object[a]; if (typeof val === 'object') { // IF OBJECT OR NULL REF. /***************************************************************************/ // DO NOT REMOVE THE [FALSE] AS THAT WOULD RESET THE [DJSHELPER.CACHE] // AND THE RESULT WOULD BE STACK OVERFLOW /***************************************************************************/ if(val !== null) { if (DJSHelper.Cache.indexOf(val) === -1) { // VAL NOT IN CACHE // ADD THE VAL TO CACHE FIRST -> BEFORE DOING RECURSION val.hashID = DJSHelper.currentHashID++; //console.log("Assigned", val.hashID, "to", a); DJSHelper.Cache.push(val); if (!(val instanceof Array)) { // VAL NOT AN [ARRAY] try { this.children.push(new DJSNode(a, val, false)); } catch (err) { console.log(err.message, a); throw err; } } else { // VAL IS AN [ARRAY] var node = new DJSNode(a, { array: true, hashID: val.hashID // HashID of array }, false); val.forEach(function (elem, index) { node.children.push(new DJSNode("elem", {val: elem}, false)); }); this.children.push(node); } } else { // VAL IN CACHE // ADD A CYCLIC NODE WITH HASH-ID this.children.push(new DJSNode(a, { cyclic: true, hashID: val.hashID }, false)); } }else{ // PUT NULL AS AN ATTRIBUTE this.attributes[a] = 'null'; } } else if (typeof val !== 'function') { // MUST BE A PRIMITIVE // ADD IT AS AN ATTRIBUTE this.attributes[a] = val; } } } if(isRoot){ DJSHelper.Cache = null; } this.constructorName = object.constructor.name; } DJSNode.Revive = function (xmlNode, isRoot) { // Default value of [isRoot] is True isRoot = typeof isRoot === 'undefined'?true: isRoot; var root; if(isRoot){ DJSHelper.ReviveCache = []; //Garbage Collect } if(window[xmlNode.constructorName].toString().indexOf('[native code]') > -1 ) { // yep, native in the browser if(xmlNode.constructorName == 'Object'){ root = {}; }else{ return null; } }else { eval('root = new ' + xmlNode.constructorName + "()"); } //CACHE ROOT INTO REVIVE-CACHE DJSHelper.ReviveCache[xmlNode.attributes.hashID] = root; for(var k in xmlNode.attributes){ // PRIMITIVE OR NULL REF FIELDS if(xmlNode.attributes.hasOwnProperty(k)) { var a = xmlNode.attributes[k]; if(a == 'null'){ root[k] = null; }else { root[k] = a; } } } xmlNode.children.forEach(function (value) { // Each children is an [DJSNode] // [Array]s are stored as [DJSNode] with an positive Array attribute // So is value if(value.attributes.array){ // ITS AN [ARRAY] root[value.name] = []; value.children.forEach(function (elem) { root[value.name].push(elem.attributes.val); }); //console.log("Caching", value.attributes.hashID); DJSHelper.ReviveCache[value.attributes.hashID] = root[value.name]; }else if(!value.attributes.cyclic){ // ITS AN [OBJECT] root[value.name] = DJSNode.Revive(value, false); //console.log("Caching", value.attributes.hashID); DJSHelper.ReviveCache[value.attributes.hashID] = root[value.name]; } }); // [SEPARATE ITERATION] TO MAKE SURE ALL POSSIBLE // [CYCLIC] REFERENCES ARE CACHED PROPERLY xmlNode.children.forEach(function (value) { // Each children is an [DJSNode] // [Array]s are stored as [DJSNode] with an positive Array attribute // So is value if(value.attributes.cyclic){ // ITS AND [CYCLIC] REFERENCE root[value.name] = DJSHelper.ReviveCache[value.attributes.hashID]; } }); if(isRoot){ DJSHelper.ReviveCache = null; //Garbage Collect } return root; }; DecycledJSON = {}; DecycledJSON.stringify = function (obj) { return JSON.stringify(new DJSNode("root", obj)); }; DecycledJSON.parse = function (json, replacerObject) { // use the replacerObject to get the null values return DJSNode.Revive(JSON.parse(json)); }; DJS = DecycledJSON; 

用法示例1:

 var obj = { id:201, box: { owner: null, key: 'storm' }, lines:[ 'item1', 23 ] }; console.log(obj); // ORIGINAL // SERIALIZE AND THEN PARSE var jsonObj = DJS.stringify(obj); console.log(DJS.parse(jsonObj)); 

用法示例2:

 // PERSON OBJECT function Person() { this.name = null; this.child = null; this.dad = null; this.mom = null; } var Dad = new Person(); Dad.name = 'John'; var Mom = new Person(); Mom.name = 'Sarah'; var Child = new Person(); Child.name = 'Kiddo'; Dad.child = Mom.child = Child; Child.dad = Dad; Child.mom = Mom; console.log(Child); // ORIGINAL // SERIALIZE AND THEN PARSE var jsonChild = DJS.stringify(Child); console.log(DJS.parse(jsonChild)); 

如果

 console.log(JSON.stringify(object)); 

导致一个

TypeError:循环对象值

那么你可能想打印这样的:

 var output = ''; for (property in object) { output += property + ': ' + object[property]+'; '; } console.log(output);