如何以类似JSON的格式打印圆形结构?

我有一个我想转换为JSON并发送的大对象。但是它有循环结构。我想丢弃任何存在的循环引用并发送任何可以字符串化的东西。我该怎么做?

谢了

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

我想把obj串成:

{"a":"foo"}
882574 次浏览

JSON.stringify与自定义替换器一起使用。例如:

// Demo: Circular referencevar circ = {};circ.circ = circ;
// Note: cache should not be re-used by repeated calls to JSON.stringify.var cache = [];JSON.stringify(circ, (key, value) => {if (typeof value === 'object' && value !== null) {// Duplicate reference found, discard keyif (cache.includes(value)) return;
// Store value in our collectioncache.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, ...);

但这个概念仍然存在:使用自定义替换程序,并跟踪解析的对象值。

作为es6中编写的实用函数:

// safely handles circular referencesJSON.safeStringify = (obj, indent = 2) => {let cache = [];const retVal = JSON.stringify(obj,(key, value) =>typeof value === "object" && value !== null? cache.includes(value)? undefined // Duplicate reference found, discard key: cache.push(value) && value // Store value in our collection: value,indent);cache = null;return retVal;};
// Example:console.log('options', JSON.safeStringify(options))

将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));

想出一种用循环引用填充替换数组的方法。您可以使用typeof方法来查找属性是否为“对象”(引用)类型,并使用精确的相等检查 ( === ) 来验证循环引用。

当您不要知道所有循环引用的键时,未来的谷歌人正在寻找解决这个问题的方法,您可以使用JSON.stringify函数周围的包装器来排除循环引用。请参阅https://gist.github.com/4653128中的示例脚本。

该解决方案本质上归结为将对先前打印对象的引用保留在数组中,并在返回值之前在替换器函数中检查该引用。它比仅排除循环引用更具限制性,因为它还排除了两次打印对象的可能性,其副作用之一是避免循环引用。

包装示例:

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);}

我真的很喜欢Trindaz的解决方案-更冗长,但它有一些错误。我也为喜欢它的人修复了它们。

另外,我在缓存对象上添加了长度限制。

如果我打印的对象真的很大——我的意思是无限大——我想限制我的算法。

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 objectsreturn 'object too long';}var printedObjIndex = false;printedObjects.forEach(function(obj, index){if(obj===value){printedObjIndex = index;}});
if ( key == ''){ //root elementprintedObjects.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);};

在Node.js,您可以使用util.inspect(对象)。它会自动将循环链接替换为“[循环]”。


尽管是内置的(无需安装),但您必须导入它

import * as util from 'util' // has no default exportimport { inspect } from 'util' // or directly// orvar util = require('util')

要使用它,只需调用

console.log(util.inspect(myObject))

还要注意,您可以将选项对象传递给检查(见上面的链接)

inspect(myObject[, options: {showHidden, depth, colors, showProxy, ...moreOptions}])


请阅读并向下面的评论者致敬…

如果

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

导致一个

TypeError:循环对象值

然后你可能想这样打印:

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

请注意,还有一个由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方法重新创建原始对象。因此,您不必从对象中删除循环来对它们进行字符串化。

然而,这将没有适用于DOM节点(这是现实生活中用例中周期的典型原因)。例如,这将抛出:

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

我已经做了一个分叉来解决这个问题(见我的cycle.js叉子)。

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

请注意,在我的分叉中,JSON.decycle(variable)与原始版本一样工作,当variable包含DOM节点/元素时,将抛出异常。

当您使用JSON.decycle(variable, true)时,您接受了结果不可逆的事实(回溯不会重新创建DOM节点)。不过,DOM元素应该在某种程度上是可识别的。例如,如果div元素有一个id,那么它将被替换为字符串"div#id-of-the-element"

我建议从@isaac中检查json-stringify-安全维护-它在NPM中使用。

BTW-如果你不使用Node.js,你可以从源代码的相关部分复制和粘贴第4-27行。

要安装:

$ npm install json-stringify-safe --save

要使用:

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

这产生:

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

请注意,就像@Rob W提到的vanillaJSON.stringify函数一样,您也可以通过传入“替换器”函数作为stringify()的第二个参数来自定义清理行为。如果您发现自己需要一个简单的示例来做到这一点,我只是编写了一个自定义替换器,它将错误、正则表达式和函数强制转换为人类可读的字符串这里

使用此类对象解决此问题的另一个解决方案是使用此库

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

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

评价为:

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

具有以下功能:

/*** 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 callcensorTheseItems = censorTheseItems || [source];//default if none is specifiedcensoredMessage = censoredMessage || "CIRCULAR_REFERENCE_REMOVED";//values that have allready apeared will be placed here:var recursiveItems = {};//initaite a censored clone to return backvar 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 isret[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 valuesfor (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 thisvalue = censoredMessage;} else {//recursion:value = preventCircularJson(value, censoredMessage, censorChildItems.concat(censorTheseItems));}ret[key] = value
}
return ret;}

我找到了github上的循环json库,它对我的问题很有效。

我发现一些有用的好功能:

  • 支持多平台使用,但到目前为止我只测试了node.js。
  • API是相同的,因此您需要做的就是包含并将其用作JSON替代品。
  • 它有自己的解析方法,因此您可以将“循环”序列化数据转换回对象。

我知道这是一个老问题,但我想推荐一个我创建的名为智能循环的NPM包,它的工作方式与其他建议的方式不同。如果您使用又大又深的物体,它特别有用。

一些特点是:

  • 将循环引用或对象内部简单重复的结构替换为导致其第一次出现的路径(不仅仅是字符串[循环]);

  • 通过在广度优先搜索中寻找循环,包确保此路径尽可能小,这在处理非常大且深的对象时很重要,其中路径可能会变得令人烦恼且难以遵循(JSON.stringify中的自定义替换执行DFS);

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

  • 最后,路径完全按照访问引用的字段所需的方式编写,这可以帮助您调试。

我这样解决这个问题:

var util = require('util');
// Our circular objectvar 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 codevar foo = new Function('return ' + str + ';');
// And have funconsole.log(JSON.stringify(foo(), null, 4));

基于其他答案,我最终得到以下代码。它适用于循环引用、带有自定义构造函数的对象。

从给定的要序列化的对象中,

  • 缓存你在遍历对象时遇到的所有对象,并为每个对象分配一个唯一的hashID(自动递增的数字也可以)
  • 找到循环引用后,将新对象中的该字段标记为循环,并将原始对象的hashID存储为属性。

github链接-DecycledJSON

DJSHelper = {};DJSHelper.Cache = [];DJSHelper.currentHashID = 0;DJSHelper.ReviveCache = [];
// DOES NOT SERIALIZE FUNCTIONfunction DJSNode(name, object, isRoot){this.name = name;// [ATTRIBUTES] contains the primitive fields of the Nodethis.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 encodingisRoot = typeof isRoot === 'undefined'? true:isRoot;this.isRoot = isRoot;if(isRoot){DJSHelper.Cache = [];DJSHelper.currentHashID = 0;
// CACHE THE ROOTobject.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 RECURSIONval.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-IDthis.children.push(new DJSNode(a, {cyclic: true,hashID: val.hashID}, false));}}else{// PUT NULL AS AN ATTRIBUTEthis.attributes[a] = 'null';}} else if (typeof val !== 'function') {// MUST BE A PRIMITIVE// ADD IT AS AN ATTRIBUTEthis.attributes[a] = val;}}}
if(isRoot){DJSHelper.Cache = null;}this.constructorName = object.constructor.name;}DJSNode.Revive = function (xmlNode, isRoot) {// Default value of [isRoot] is TrueisRoot = 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 browserif(xmlNode.constructorName == 'Object'){root = {};}else{return null;}}else {eval('root = new ' + xmlNode.constructorName + "()");}
//CACHE ROOT INTO REVIVE-CACHEDJSHelper.ReviveCache[xmlNode.attributes.hashID] = root;
for(var k in xmlNode.attributes){// PRIMITIVE OR NULL REF FIELDSif(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 PROPERLYxmlNode.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] REFERENCEroot[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 valuesreturn 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 PARSEvar 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 PARSEvar jsonChild = DJS.stringify(Child);console.log(DJS.parse(jsonChild));

只是做

npm i --save circular-json

然后在你的js文件中

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

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

注意:我与这个包无关。但我确实用它来做这件事。

更新2020

请注意,CircularJSON仅在维护中,扁平是其继任者。

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

Object.defineProperties(JSON, {refStringify: {value: function(obj) {
let objMap = new Map();let stringified = JSON.stringify(obj,function(key, value) {
// only for objectsif (typeof value == 'object') {// If has the value then return a reference to itif (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;}},});
// *************************** Examplelet a = {b: 32,c: {get a() {return a;},get c() {return a.c;}}};let stringified = JSON.refStringify(a);let parsed = JSON.refParse(stringified, 2);console.log(parsed, JSON.refStringify(parsed));// *************************** /Example
// *************************** Helperfunction _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);});}

@RobW的答案是正确的,但这更有效!因为它使用了哈希映射/集合:

const customStringify = function (v) {const cache = new Set();return JSON.stringify(v, function (key, value) {if (typeof value === 'object' && value !== null) {if (cache.has(value)) {// Circular reference foundtry {// If this value does not reference a parent it can be dedupedreturn JSON.parse(JSON.stringify(value));}catch (err) {// discard key if value cannot be dedupedreturn;}}// Store value in our setcache.add(value);}return value;});};

试试这个:

var obj = {a: "foo",b: obj};
var circular_replacer = (value) => {var seen = [];if (value != null && typeof value == "object") {if (seen.indexOf(value) >= 0) return;seen.push(value);}return value;};
obj = circular_replacer(obj);

JSON.stringify()的第二个参数允许您指定一个键名数组,该数组应该从数据中遇到的每个对象中保留。这可能不适用于所有用例,但这是一个更简单的解决方案。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify

var obj = {a: "foo",b: this}
var json = JSON.stringify(obj, ['a']);console.log(json);// {"a":"foo"}

备注:奇怪的是,OP中的对象定义不会在最新的Chrome或Firefox中抛出循环引用错误。这个答案中的定义被修改为确实抛出错误。


我想知道为什么还没有人发布来自MDN页面的正确解决方案

const circularReference = {otherData: 123};circularReference.myself = circularReference;
const getCircularReplacer = () => {const seen = new WeakSet();return (key, value) => {if (typeof value === "object" && value !== null) {if (seen.has(value)) {return;}seen.add(value);}return value;};};
const stringified = JSON.stringify(circularReference, getCircularReplacer());
console.log(stringified);

看到的值应该存储在在一组中,而不是数组中(替换器被称为在每个元素)。

就像在接受的答案中一样,这个解决方案删除了所有重复值,而不仅仅是圆形的。但至少它没有指数级的复杂性。

尽管这已经得到了充分的回答,但您也可以在使用delete运算符进行字符串化之前显式删除有问题的属性。

delete obj.b;const jsonObject = JSON.stringify(obj);

删除操作符

这将消除构建或维护复杂逻辑以删除循环引用的需要。

要更新覆盖JSON工作方式的答案(可能不推荐,但非常简单),请不要使用circular-json(不建议使用)。相反,使用后继,扁平:

https://www.npmjs.com/package/flatted

从上面@user1541685的旧答案中借用,但替换为新答案:

npm i --save flatted

然后在你的js文件中

const CircularJSON = require('flatted');const json = CircularJSON.stringify(obj);
function myStringify(obj, maxDeepLevel = 2) {if (obj === null) {return 'null';}if (obj === undefined) {return 'undefined';}if (maxDeepLevel < 0 || typeof obj !== 'object') {return obj.toString();}return Object.entries(obj).map(x => x[0] + ': ' + myStringify(x[1], maxDeepLevel - 1)).join('\r\n');}

您可以尝试JSON解析器库:treedoc。它支持循环引用,还可以使用引用对重复对象进行消重。

yarn add treedoc

import {TD} from 'treedoc'TD.stringify(obj);

如果你想要更多的定制

import {TD, TDEncodeOption} from 'treedoc'
const opt = new TDEncodeOption();opt.coderOption.setShowType(true).setShowFunction(true);opt.jsonOption.setIndentFactor(2);return TD.stringify(obj, opt);

生成的JSON文件可以由查看器http://treedoc.org查看,它支持通过JSON节点引用进行导航。

[无耻塞]我是这个库的作者

这个线程中的大多数答案都是专门针对JSON.stringify使用的——它们没有显示如何实际删除原始对象树中的循环引用。(好吧,除了之后再次调用JSON.parse——这需要重新分配,并且具有更高的性能影响)

为了从源对象树中删除循环引用,您可以使用这样的函数:https://stackoverflow.com/a/63952549/2441655

然后,这些通用的循环引用移除函数可用于安全地对循环引用敏感函数(如JSON.stringify)进行后续调用:

const objTree = {normalProp: true};objTree.selfReference = objTree;RemoveCircularLinks(objTree); // without this line, the JSON.stringify call errorsconsole.log(JSON.stringify(objTree));

我们使用对象扫描进行数据处理,这可能是一个可行的解决方案。这就是它的工作原理(也正确修剪数组)

.as-console-wrapper {max-height: 100% !important; top: 0}
<script type="module">import objectScan from 'https://cdn.jsdelivr.net/npm/object-scan@18.1.2/lib/index.min.js';
const prune = (data) => objectScan(['**'], {rtn: 'count',filterFn: ({ isCircular, parent, property }) => {if (isCircular) {if (Array.isArray(parent)) {parent.splice(property, 1);} else {delete parent[property];}return true;}return false;},breakFn: ({ isCircular }) => isCircular === true})(data);
const obj = { a: 'foo', c: [0] };obj.b = obj;obj.c.push(obj);console.log(obj);// => <ref *1> { a: 'foo', c: [ 0, [Circular *1] ], b: [Circular *1] }
console.log(prune(obj)); // returns circular counts// => 2
console.log(obj);// => { a: 'foo', c: [ 0 ] }</script>

免责声明:我是对象扫描的作者

循环引用此代码将失败:

    JSON.stringify(circularReference);// TypeError: cyclic object value

使用下面的代码:

 const getCircularReplacer = () => {const seen = new WeakSet();return (key, value) => {if (typeof value === "object" && value !== null) {if (seen.has(value)) {return;}seen.add(value);}return value;};};
JSON.stringify(circularReference, getCircularReplacer());

我为日志实用程序类创建了以下方法。以下方法获取源和目标对象,并通过给定的maxLevel将源分配给目标。

  static assignObjectByLevel(sourceObject: any,targetObject: any,currentLevel: number = 0,maxLevel: number = 3,showUndefinedValues = false): any {if (currentLevel >= maxLevel) {return;}
const objQueue = [];for (const key in sourceObject) {if (sourceObject.hasOwnProperty(key)) {const value = sourceObject[key];if (typeof value === "object") {objQueue.push({ key, value });} else {targetObject[key] = value;}} else {if (showUndefinedValues) {targetObject[key] = "undefined/null";}}}
while (objQueue.length > 0) {const objVal = objQueue.pop();currentLevel++;targetObject[objVal.key] = {};this.assignObjectByLevel(objVal.value,targetObject[objVal.key],currentLevel,maxLevel,false);}}

使用示例:

   const logObjParam = {level1: "value1",level2: {value2: "value2",level3: {value3: "value3",level4: {value4: " value4",level5: {value5: " value5",},},},},};
let logObj = {};this.assignObjectByLevel(logObjParam, logObj);

结果:

{"level1": "value1","level2": {"value2": "value2","level3": {"value3": "value3","level4": {}}}}

这里有一个解决方案:

  • 删除循环只有(而不是所有重复对象引用,就像迄今为止发布的大多数解决方案一样),
  • 不是不必要的冗长,
  • 是快,
  • 不需要任何库依赖项。
function replaceCycles(obj, replacement = undefined, seen = new WeakSet()) {if (typeof obj === 'object')if (seen.has(obj))return replacementelse {seen.add(obj)const newObj = {}for (const key in obj)newObj[key] = replaceCycles(obj[key], replacement, seen)seen.delete(obj)return newObj}elsereturn obj}

用法:

const a = {b: 'v1',c: {d: 'v2'}}
a.e = a.ca.c.f = a.c
console.log(JSON.stringify(replaceCycles(a, '[CYCLE]')))

输出:

"{'b':'v1','c':{'d':'v2','f':'[CYCLE]'},'e':{'d':'v2','f':'[CYCLE]'}}"

超串行完全序列化JavaScript对象。

https://github.com/denostack/superserial

用法:

const serializer = new Serializer();
const nodes = [{ self: null as any, siblings: [] as any[] }, {self: null as any,siblings: [] as any[],}];nodes[0].self = nodes[0];nodes[0].siblings = nodes;nodes[1].self = nodes[1];nodes[1].siblings = nodes;
const serialized = serializer.serialize(nodes);
console.log(serialized);

输出:

[$1,$2];{"self":$1,"siblings":$0};{"self":$2,"siblings":$0}

此解决方案修复了user2451227接受的答案when o = {}; JSON.stringify([o, o], getCircularReplacer()))上报告的问题

function newCircularReplacer () {const seenValues = []return circularReplacer
function circularReplacer (key, value) {if (typeof value === 'object' && value !== null && Object.keys(value).length) {const stackSize= seenValues.lengthif (stackSize) {for (let n = stackSize - 1; seenValues[n][key] !== value; --n)seenValues.pop() // clean up expired referencesif (seenValues.includes(value)) return '[Circular]'}seenValues.push(value)}return value}}
let o = {a: 1}o.b = o // Circular referenceconsole.log(JSON.stringify(o, newCircularReplacer()) // {a:1,b:[Circular]} ✅)
o = {}a = [o, o] // NOT circular referenceconsole.log(JSON.stringify(a, newCircularReplacer()) // [{},{}] ✅)