鉴于我在一个大的JavaScript对象中有一个循环引用
我试试 JSON.stringify(problematicObject)
而浏览器抛出
"TypeError:将循环结构转换为JSON"
(预计)
那么我想找到这个循环引用的原因,最好使用Chrome开发人员工具?这可能吗?如何在大型对象中查找和修复循环引用?
Tre*_*ack 51
来自http://blog.vjeux.com/2011/javascript/cyclic-object-detection.html.添加一行以检测循环的位置.将其粘贴到Chrome开发工具中:
function isCyclic (obj) {
var seenObjects = [];
function detect (obj) {
if (obj && typeof obj === 'object') {
if (seenObjects.indexOf(obj) !== -1) {
return true;
}
seenObjects.push(obj);
for (var key in obj) {
if (obj.hasOwnProperty(key) && detect(obj[key])) {
console.log(obj, 'cycle at ' + key);
return true;
}
}
}
return false;
}
return detect(obj);
}
Run Code Online (Sandbox Code Playgroud)
这是测试:
> a = {}
> b = {}
> a.b = b; b.a = a;
> isCyclic(a)
Object {a: Object}
"cycle at a"
Object {b: Object}
"cycle at b"
true
Run Code Online (Sandbox Code Playgroud)
Aar*_*n V 42
当我发现这个问题时,@ tmack的答案绝对是我所寻找的!
不幸的是,它会返回许多误报 - 如果在JSON中复制了一个对象,它将返回true,这与圆度不同.圆度意味着一个对象是它自己的孩子,例如
obj.key1.key2.[...].keyX === obj
Run Code Online (Sandbox Code Playgroud)
我修改了原来的答案,这对我有用:
function isCyclic(obj) {
var keys = [];
var stack = [];
var stackSet = new Set();
var detected = false;
function detect(obj, key) {
if (obj && typeof obj != 'object') { return; }
if (stackSet.has(obj)) { // it's cyclic! Print the object and its locations.
var oldindex = stack.indexOf(obj);
var l1 = keys.join('.') + '.' + key;
var l2 = keys.slice(0, oldindex + 1).join('.');
console.log('CIRCULAR: ' + l1 + ' = ' + l2 + ' = ' + obj);
console.log(obj);
detected = true;
return;
}
keys.push(key);
stack.push(obj);
stackSet.add(obj);
for (var k in obj) { //dive on the object's children
if (Object.prototype.hasOwnProperty.call(obj, k)) { detect(obj[k], k); }
}
keys.pop();
stack.pop();
stackSet.delete(obj);
return;
}
detect(obj, 'obj');
return detected;
}
Run Code Online (Sandbox Code Playgroud)
以下是一些非常简单的测试:
var root = {}
var leaf = {'isleaf':true};
var cycle2 = {l:leaf};
var cycle1 = {c2: cycle2, l:leaf};
cycle2.c1 = cycle1
root.leaf = leaf
isCyclic(cycle1); // returns true, logs "CIRCULAR: obj.c2.c1 = obj"
isCyclic(cycle2); // returns true, logs "CIRCULAR: obj.c1.c2 = obj"
isCyclic(leaf); // returns false
isCyclic(root); // returns false
Run Code Online (Sandbox Code Playgroud)
这是我的CircularReferenceDetector类,它输出循环引用值实际所在的所有属性堆栈信息,还显示了罪魁祸首引用的位置。
这对于巨大的结构特别有用,其中关键是哪个值是危害的来源并不明显。
它输出字符串化的循环引用值,但所有对自身的引用都替换为“[Circular object --- fix me]”。
用法:
CircularReferenceDetector.detectCircularReferences(value);
注意: 如果您不想使用任何日志记录或没有可用的记录器,请删除 Logger.* 语句。
技术说明:
递归函数遍历对象的所有属性并测试 JSON.stringify 是否成功。如果它不成功(循环引用),则它通过用某个常量字符串替换 value 本身来测试它是否成功。这意味着如果它成功使用这个替换器,这个值就是被循环引用的值。如果不是,它将递归遍历该对象的所有属性。
同时,它还跟踪属性堆栈,为您提供罪魁祸首值所在的信息。
import {Logger} from "../Logger";
export class CircularReferenceDetector {
static detectCircularReferences(toBeStringifiedValue: any, serializationKeyStack: string[] = []) {
Object.keys(toBeStringifiedValue).forEach(key => {
var value = toBeStringifiedValue[key];
var serializationKeyStackWithNewKey = serializationKeyStack.slice();
serializationKeyStackWithNewKey.push(key);
try {
JSON.stringify(value);
Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is ok`);
} catch (error) {
Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" JSON.stringify results in error: ${error}`);
var isCircularValue:boolean;
var circularExcludingStringifyResult:string = "";
try {
circularExcludingStringifyResult = JSON.stringify(value, CircularReferenceDetector.replaceRootStringifyReplacer(value), 2);
isCircularValue = true;
} catch (error) {
Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is not the circular source`);
CircularReferenceDetector.detectCircularReferences(value, serializationKeyStackWithNewKey);
isCircularValue = false;
}
if (isCircularValue) {
throw new Error(`Circular reference detected:\nCircularly referenced value is value under path "${Util.joinStrings(serializationKeyStackWithNewKey)}" of the given root object\n`+
`Calling stringify on this value but replacing itself with [Circular object --- fix me] ( <-- search for this string) results in:\n${circularExcludingStringifyResult}\n`);
}
}
});
}
private static replaceRootStringifyReplacer(toBeStringifiedValue: any): any {
var serializedObjectCounter = 0;
return function (key: any, value: any) {
if (serializedObjectCounter !== 0 && typeof(toBeStringifiedValue) === 'object' && toBeStringifiedValue === value) {
Logger.error(`object serialization with key ${key} has circular reference to being stringified object`);
return '[Circular object --- fix me]';
}
serializedObjectCounter++;
return value;
}
}
}
export class Util {
static joinStrings(arr: string[], separator: string = ":") {
if (arr.length === 0) return "";
return arr.reduce((v1, v2) => `${v1}${separator}${v2}`);
}
}
Run Code Online (Sandbox Code Playgroud)
"use strict";
const Logger_1 = require("../Logger");
class CircularReferenceDetector {
static detectCircularReferences(toBeStringifiedValue, serializationKeyStack = []) {
Object.keys(toBeStringifiedValue).forEach(key => {
var value = toBeStringifiedValue[key];
var serializationKeyStackWithNewKey = serializationKeyStack.slice();
serializationKeyStackWithNewKey.push(key);
try {
JSON.stringify(value);
Logger_1.Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is ok`);
}
catch (error) {
Logger_1.Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" JSON.stringify results in error: ${error}`);
var isCircularValue;
var circularExcludingStringifyResult = "";
try {
circularExcludingStringifyResult = JSON.stringify(value, CircularReferenceDetector.replaceRootStringifyReplacer(value), 2);
isCircularValue = true;
}
catch (error) {
Logger_1.Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is not the circular source`);
CircularReferenceDetector.detectCircularReferences(value, serializationKeyStackWithNewKey);
isCircularValue = false;
}
if (isCircularValue) {
throw new Error(`Circular reference detected:\nCircularly referenced value is value under path "${Util.joinStrings(serializationKeyStackWithNewKey)}" of the given root object\n` +
`Calling stringify on this value but replacing itself with [Circular object --- fix me] ( <-- search for this string) results in:\n${circularExcludingStringifyResult}\n`);
}
}
});
}
static replaceRootStringifyReplacer(toBeStringifiedValue) {
var serializedObjectCounter = 0;
return function (key, value) {
if (serializedObjectCounter !== 0 && typeof (toBeStringifiedValue) === 'object' && toBeStringifiedValue === value) {
Logger_1.Logger.error(`object serialization with key ${key} has circular reference to being stringified object`);
return '[Circular object --- fix me]';
}
serializedObjectCounter++;
return value;
};
}
}
exports.CircularReferenceDetector = CircularReferenceDetector;
class Util {
static joinStrings(arr, separator = ":") {
if (arr.length === 0)
return "";
return arr.reduce((v1, v2) => `${v1}${separator}${v2}`);
}
}
exports.Util = Util;
Run Code Online (Sandbox Code Playgroud)
您也可以JSON.stringify
与try/catch 一起使用
function hasCircularDependency(obj)
{
try
{
JSON.stringify(obj);
}
catch(e)
{
return e.includes("Converting circular structure to JSON");
}
return false;
}
Run Code Online (Sandbox Code Playgroud)
演示
function hasCircularDependency(obj)
{
try
{
JSON.stringify(obj);
}
catch(e)
{
return e.includes("Converting circular structure to JSON");
}
return false;
}
Run Code Online (Sandbox Code Playgroud)
这里有很多答案,但我想我应该将我的解决方案添加到其中。它类似于@Trey Mack的答案,但该解决方案需要 O(n^2)。该版本使用WeakMap
数组代替,将时间提高到 O(n)。
function isCyclic(object) {
const seenObjects = new WeakMap(); // use to keep track of which objects have been seen.
function detectCycle(obj) {
// If 'obj' is an actual object (i.e., has the form of '{}'), check
// if it's been seen already.
if (Object.prototype.toString.call(obj) == '[object Object]') {
if (seenObjects.has(obj)) {
return true;
}
// If 'obj' hasn't been seen, add it to 'seenObjects'.
// Since 'obj' is used as a key, the value of 'seenObjects[obj]'
// is irrelevent and can be set as literally anything you want. I
// just went with 'undefined'.
seenObjects.set(obj, undefined);
// Recurse through the object, looking for more circular references.
for (var key in obj) {
if (detectCycle(obj[key])) {
return true;
}
}
// If 'obj' is an array, check if any of it's elements are
// an object that has been seen already.
} else if (Array.isArray(obj)) {
for (var i in obj) {
if (detectCycle(obj[i])) {
return true;
}
}
}
return false;
}
return detectCycle(object);
}
Run Code Online (Sandbox Code Playgroud)
这就是它实际的样子。
> var foo = {grault: {}};
> detectCycle(foo);
false
> foo.grault = foo;
> detectCycle(foo);
true
> var bar = {};
> detectCycle(bar);
false
> bar.plugh = [];
> bar.plugh.push(bar);
> detectCycle(bar);
true
Run Code Online (Sandbox Code Playgroud)
小智 5
这是针对条件上的@Trey Mack和@Freddie Nfbnm答案的修复程序typeof obj != 'object'
。相反,它应该测试该obj
值是否不是对象的实例,以便它在检查对象熟悉的值时也可以工作(例如,函数和符号(符号不是对象的实例,但仍然是对象)。
我将其发布为答案,因为我尚无法在此StackExchange帐户中发表评论。
PS .:随时要求我删除此答案。
function isCyclic(obj) {
var keys = [];
var stack = [];
var stackSet = new Set();
var detected = false;
function detect(obj, key) {
if (!(obj instanceof Object)) { return; } // Now works with other
// kinds of object.
if (stackSet.has(obj)) { // it's cyclic! Print the object and its locations.
var oldindex = stack.indexOf(obj);
var l1 = keys.join('.') + '.' + key;
var l2 = keys.slice(0, oldindex + 1).join('.');
console.log('CIRCULAR: ' + l1 + ' = ' + l2 + ' = ' + obj);
console.log(obj);
detected = true;
return;
}
keys.push(key);
stack.push(obj);
stackSet.add(obj);
for (var k in obj) { //dive on the object's children
if (obj.hasOwnProperty(k)) { detect(obj[k], k); }
}
keys.pop();
stack.pop();
stackSet.delete(obj);
return;
}
detect(obj, 'obj');
return detected;
}
Run Code Online (Sandbox Code Playgroud)
这是一个从@Aaron V和@user4976005的答案混合而成的 Node ES6 版本,它解决了调用 hasOwnProperty 的问题:
const isCyclic = (obj => {
const keys = []
const stack = []
const stackSet = new Set()
let detected = false
const detect = ((object, key) => {
if (!(object instanceof Object))
return
if (stackSet.has(object)) { // it's cyclic! Print the object and its locations.
const oldindex = stack.indexOf(object)
const l1 = `${keys.join('.')}.${key}`
const l2 = keys.slice(0, oldindex + 1).join('.')
console.log(`CIRCULAR: ${l1} = ${l2} = ${object}`)
console.log(object)
detected = true
return
}
keys.push(key)
stack.push(object)
stackSet.add(object)
Object.keys(object).forEach(k => { // dive on the object's children
if (k && Object.prototype.hasOwnProperty.call(object, k))
detect(object[k], k)
})
keys.pop()
stack.pop()
stackSet.delete(object)
})
detect(obj, 'obj')
return detected
})
Run Code Online (Sandbox Code Playgroud)
以下是 MDNJSON.stringify()
在循环对象上使用时检测和修复循环引用的方法:https : //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value:
在如下所示的圆形结构中
var circularReference = {otherData: 123};
circularReference.myself = circularReference;
Run Code Online (Sandbox Code Playgroud)
JSON.stringify()
将失败:
JSON.stringify(circularReference);
// TypeError: cyclic object value
Run Code Online (Sandbox Code Playgroud)
要序列化循环引用,您可以使用支持它们的库(例如cycle.js)或自己实现解决方案,这将需要通过可序列化值查找和替换(或删除)循环引用。
下面的代码片段说明了如何使用的替换器参数查找和过滤(从而导致数据丢失)循环引用:
JSON.stringify()
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());
// {"otherData":123}
Run Code Online (Sandbox Code Playgroud)
Siv*_*ran -1
尝试console.log()
在 chrome/firefox 浏览器上使用来确定问题所在。
在 Firefox 上使用 Firebug 插件,您可以逐行调试 javascript。
请参阅下面的循环引用问题示例并已处理:-
// JSON.stringify, avoid TypeError: Converting circular structure to JSON
// Demo: Circular reference
var o = {};
o.o = o;
var cache = [];
JSON.stringify(o, function(key, value) {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
// Circular reference found, discard key
alert("Circular reference found, discard key");
return;
}
alert("value = '" + value + "'");
// Store value in our collection
cache.push(value);
}
return value;
});
cache = null; // Enable garbage collection
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);
var obj = {
a: "foo",
b: obj
};
var replacement = {"b":undefined};
alert("Result : " + JSON.stringify(obj,replacement));
Run Code Online (Sandbox Code Playgroud)
请参阅示例现场演示
归档时间: |
|
查看次数: |
34470 次 |
最近记录: |