大きな JavaScript オブジェクトに循環参照があるとします。
そして、私は試しますJSON.stringify(problematicObject)
そして、ブラウザがスローします
「TypeError: 循環構造を JSON に変換しています」
(これは予想されます)
次に、できれば Chrome 開発者ツールを使用して、この循環参照の原因を見つけたいですか? これは可能ですか?大きなオブジェクトの循環参照をどのように見つけて修正しますか?
大きな JavaScript オブジェクトに循環参照があるとします。
そして、私は試しますJSON.stringify(problematicObject)
そして、ブラウザがスローします
「TypeError: 循環構造を JSON に変換しています」
(これは予想されます)
次に、できれば Chrome 開発者ツールを使用して、この循環参照の原因を見つけたいですか? これは可能ですか?大きなオブジェクトの循環参照をどのように見つけて修正しますか?
http://blog.vjeux.com/2011/javascript/cyclic-object-detection.htmlから取得。サイクルがどこにあるかを検出するために1行が追加されました。これを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);
}
テストは次のとおりです。
> a = {}
> b = {}
> a.b = b; b.a = a;
> isCyclic(a)
Object {a: Object}
"cycle at a"
Object {b: Object}
"cycle at b"
true
これは、条件に関する@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;
}
これは、循環参照された値が実際に配置されているすべてのプロパティ スタック情報を出力し、犯人参照がどこにあるかを示す私のCircularReferenceDetectorクラスです。
これは、どの値が害の原因であるかがキーによって明らかでない巨大な構造に特に役立ちます。
文字列化された循環参照値を出力しますが、それ自体へのすべての参照は「[Circular object --- fix me]」に置き換えられます。
使用法:
CircularReferenceDetector.detectCircularReferences(value);
注: ロギングを使用したくない場合、または使用可能なロガーがない場合は、Logger.* ステートメントを削除してください。
技術的な説明:
再帰関数は、オブジェクトのすべてのプロパティを調べて、JSON.stringify が成功するかどうかをテストします。成功しない場合 (循環参照) は、値自体を定数文字列に置き換えて成功するかどうかをテストします。これは、この置換を使用して成功した場合、この値が循環参照されていることを意味します。そうでない場合は、そのオブジェクトのすべてのプロパティを再帰的に調べます。
同時に、プロパティ スタックも追跡して、犯人の値がどこにあるかを示します。
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}`);
}
}
"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;
ここにはたくさんの答えがありますが、私のソリューションをミックスに追加すると思いました。@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);
}
そして、これが実際の動作の様子です。
> 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
私はちょうどこれを作った。汚いかもしれませんが、とにかく動作します... :P
function dump(orig){
var inspectedObjects = [];
console.log('== DUMP ==');
(function _dump(o,t){
console.log(t+' Type '+(typeof o));
for(var i in o){
if(o[i] === orig){
console.log(t+' '+i+': [recursive]');
continue;
}
var ind = 1+inspectedObjects.indexOf(o[i]);
if(ind>0) console.log(t+' '+i+': [already inspected ('+ind+')]');
else{
console.log(t+' '+i+': ('+inspectedObjects.push(o[i])+')');
_dump(o[i],t+'>>');
}
}
}(orig,'>'));
}
それで
var a = [1,2,3], b = [a,4,5,6], c = {'x':a,'y':b};
a.push(c); dump(c);
言う
== DUMP ==
> Type object
> x: (1)
>>> Type object
>>> 0: (2)
>>>>> Type number
>>> 1: (3)
>>>>> Type number
>>> 2: (4)
>>>>> Type number
>>> 3: [recursive]
> y: (5)
>>> Type object
>>> 0: [already inspected (1)]
>>> 1: (6)
>>>>> Type number
>>> 2: (7)
>>>>> Type number
>>> 3: (8)
>>>>> Type number
これは、cx[3] が c に等しく、cx = cy[0] であることを示しています。
または、この関数を少し編集すると、必要なものがわかります...
function findRecursive(orig){
var inspectedObjects = [];
(function _find(o,s){
for(var i in o){
if(o[i] === orig){
console.log('Found: obj.'+s.join('.')+'.'+i);
return;
}
if(inspectedObjects.indexOf(o[i])>=0) continue;
else{
inspectedObjects.push(o[i]);
s.push(i); _find(o[i],s); s.pop(i);
}
}
}(orig,[]));
}
console.log()
問題が発生した場所を特定するために、chrome/firefox ブラウザで使用してみてください。
Firebug プラグインを使用する Firefox では、JavaScript を 1 行ずつデバッグできます。
以下の循環参照の問題の例を参照してください。
// 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));
参照例LIVE DEMO