Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detecting and fixing circular references in JavaScript

Tags:

javascript

Given I have a circular reference in a large JavaScript object

And I try JSON.stringify(problematicObject)

And the browser throws

"TypeError: Converting circular structure to JSON"

(which is expected)

Then I want to find the cause of this circular reference, preferably using Chrome developer tools? Is this possible? How do you find and fix circular references in a large object?

like image 831
Mads Mobæk Avatar asked Feb 19 '13 16:02

Mads Mobæk


People also ask

What are circular references in JavaScript?

A circular reference occurs if two separate objects pass references to each other. In older browsers circular references were a cause of memory leaks. With improvements in Garbage collection algorithms, which can now handle cycles and cyclic dependencies fine, this is no longer an issue.

Are circular references bad JavaScript?

However, from a pure design point of view, circular referencing is still a bad thing and a code smell. Circular referencing implies that the 2 objects referencing each other are tightly coupled and changes to one object may need changes in other as well. There is no one way to avoid circular reference in JS.

How do you solve a circular reference in oops?

Circular reference occurs when two or more interdependent resources cause lock condition. This makes the resource unusable. To handle the problem of circular references in C#, you should use garbage collection. It detects and collects circular references.

What is wrong with circular references?

The circular reference error message "There are one or more circular references where a formula refers to its own cell either directly or indirectly. This might cause them to calculate incorrectly. Try removing or changing these references, or moving the formulas to different cells."


2 Answers

Pulled from http://blog.vjeux.com/2011/javascript/cyclic-object-detection.html. One line added to detect where the cycle is. Paste this into the Chrome dev tools:

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

Here's the test:

> a = {} > b = {} > a.b = b; b.a = a; > isCyclic(a)   Object {a: Object}    "cycle at a"   Object {b: Object}    "cycle at b"   true 
like image 174
Trey Mack Avatar answered Oct 13 '22 06:10

Trey Mack


@tmack's answer is definitely what I was looking for when I found this question!

Unfortunately it returns many false positives - it returns true if an object is replicated in the JSON, which isn't the same as circularity. Circularity means that an object is its own child, e.g.

obj.key1.key2.[...].keyX === obj 

I modified the original answer, and this is working for me:

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

Here are a few very simple tests:

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 
like image 30
Aaron V Avatar answered Oct 13 '22 04:10

Aaron V