Detecting and fixing circular references in JavaScript

Javascript

Javascript Problem Overview


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?

Javascript Solutions


Solution 1 - Javascript

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

Solution 2 - Javascript

@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

Solution 3 - Javascript

Here is MDN's approach to detecting and fixing circular references when using JSON.stringify() on circular objects: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value :

> In a circular structure like the following

var circularReference = {otherData: 123};
circularReference.myself = circularReference;

> JSON.stringify() will fail:

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

> To serialize circular references you can use a library that supports them (e.g. cycle.js) or implement a solution by yourself, which will require finding and replacing (or removing) the cyclic references by serializable values.

> The snippet below illustrates how to find and filter (thus causing data loss) a cyclic reference by using the replacer parameter of 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}

Solution 4 - Javascript

CircularReferenceDetector

Here is my CircularReferenceDetector class which outputs all the property stack information where the circularly referenced value is actually located at and also shows where the culprit references are.

This is especially useful for huge structures where it is not obvious by the key which value is the source of the harm.

It outputs the circularly referenced value stringified but all references to itself replaced by "[Circular object --- fix me]".

Usage:
CircularReferenceDetector.detectCircularReferences(value);

Note: Remove the Logger.* statements if you do not want to use any logging or do not have a logger available.

Technical Explanation:
The recursive function goes through all properties of the object and tests if JSON.stringify succeeds on them or not. If it does not succeed (circular reference), then it tests if it succeeds by replacing value itself with some constant string. This would mean that if it succeeds using this replacer, this value is the being circularly referenced value. If it is not, it recursively goes through all properties of that object.

Meanwhile it also tracks the property stack to give you information where the culprit value is located at.

Typescript

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

}

Compiled JavaScript from TypeScript

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

Solution 5 - Javascript

This is a fix for both @Trey Mack and @Freddie Nfbnm answers on the typeof obj != 'object' condition. Instead it should test if the obj value is not instance of object, so that it can also work when checking values with object familiarity (for example, functions and symbols (symbols aren't instance of object, but still addressed, btw.)).

I'm posting this as an answer since I can't comment in this StackExchange account yet.

PS.: feel free to request me to delete this answer.

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

Solution 6 - Javascript

You can also use JSON.stringify with try/catch

function hasCircularDependency(obj)
{
    try
    {
        JSON.stringify(obj);
    }
    catch(e)
    {
        return e.includes("Converting circular structure to JSON"); 
    }
    return false;
}

Demo

function hasCircularDependency(obj) {
  try {
    JSON.stringify(obj);
  } catch (e) {
    return String(e).includes("Converting circular structure to JSON");
  }
  return false;
}

var a = {b:{c:{d:""}}};
console.log(hasCircularDependency(a));
a.b.c.d = a;
console.log(hasCircularDependency(a));

Solution 7 - Javascript

There's a lot of answers here, but I thought I'd add my solution to the mix. It's similar to @Trey Mack's answer, but that solution takes O(n^2). This version uses WeakMap instead of an array, improving the time to 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);
}

And this is what it looks like in action.

> 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

Solution 8 - Javascript

Here is a Node ES6 version mixed from the answers from @Aaron V and @user4976005, it fixes the problem with the call to 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
})

Solution 9 - Javascript

You can also use Symbols - thanks to that approach you won't have to mutate properties of the original object, apart from adding symbol for marking visited node.

It's cleaner and should be faster than gathering node properties and comparing with the object. It also has optional depth limitation if you don't want to serialize big nested values:

// Symbol used to mark already visited nodes - helps with circular dependencies
const visitedMark = Symbol('VISITED_MARK');

const MAX_CLEANUP_DEPTH = 10;

function removeCirculars(obj, depth = 0) {
  if (!obj) {
    return obj;
  }

  // Skip condition - either object is falsy, was visited or we go too deep
  const shouldSkip = !obj || obj[visitedMark] || depth > MAX_CLEANUP_DEPTH;

  // Copy object (we copy properties from it and mark visited nodes)
  const originalObj = obj;
  let result = {};

  Object.keys(originalObj).forEach((entry) => {
    const val = originalObj[entry];

    if (!shouldSkip) {
      if (typeof val === 'object') { // Value is an object - run object sanitizer
        originalObj[visitedMark] = true; // Mark current node as "seen" - will stop from going deeper into circulars
        const nextDepth = depth + 1;
        result[entry] = removeCirculars(val, nextDepth);
      } else {
        result[entry] = val;
      }
    } else {
      result = 'CIRCULAR';
    }
  });

  return result;
}

This will result in an object that has all the circular dependencies stripped and also does not go deeper than given MAX_CLEANUP_DEPTH.

Using symbols is safe as long as you don't do any meta-programming stuff on the object - they are transparent and they are not enumerable, hence - they will not show in any standard operations on the object.

Also, returning a new, cleaned up object has an advantage of not mutating the original one if you need to perform any additional operations on it.

If you don't want CIRCULAR marking, you can just modify the code a bit, hence skipping object before actually performing operations on it (inside the loop):

 originalObj[visitedMark] = true; // Mark current node as "seen" - will stop from going deeper into circulars
 const val = originalObj[entry];

 // Skip condition - either object is falsy, was visited or we go too deep
 const shouldSkip = val[visitedMark] || depth > MAX_SANITIZATION_DEPTH;

 if (!shouldSkip) {
   if (typeof val === 'object') { // Value is an object - run object sanitizer
    const nextDepth = depth + 1;
    result[entry] = removeCirculars(val, nextDepth);
  } else {
    result[entry] = val;
  }
 }

Solution 10 - Javascript

I just made this. It may be dirty, but works anyway... :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,'>'));
}

Then

var a = [1,2,3], b = [a,4,5,6], c = {'x':a,'y':b};
a.push(c); dump(c);

Says

== 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

This tells that c.x[3] is equal to c, and c.x = c.y[0].

Or, a little edit to this function can tell you what you need...

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,[]));
}

Solution 11 - Javascript

Here is @Thomas's answer adapted for node:

const {logger} = require("../logger")
// Or: const logger = {debug: (...args) => console.log.call(console.log, args) }

const joinStrings = (arr, separator) => {
  if (arr.length === 0) return "";
  return arr.reduce((v1, v2) => `${v1}${separator}${v2}`);
}

exports.CircularReferenceDetector = class CircularReferenceDetector {

  detectCircularReferences(toBeStringifiedValue, serializationKeyStack = []) {
    Object.keys(toBeStringifiedValue).forEach(key => {
      let value = toBeStringifiedValue[key];

      let serializationKeyStackWithNewKey = serializationKeyStack.slice();
      serializationKeyStackWithNewKey.push(key);
      try {
        JSON.stringify(value);
        logger.debug(`path "${joinStrings(serializationKeyStack)}" is ok`);
      } catch (error) {
        logger.debug(`path "${joinStrings(serializationKeyStack)}" JSON.stringify results in error: ${error}`);

        let isCircularValue;
        let circularExcludingStringifyResult = "";
        try {
          circularExcludingStringifyResult = JSON.stringify(value, this.replaceRootStringifyReplacer(value), 2);
          isCircularValue = true;
        } catch (error) {
          logger.debug(`path "${joinStrings(serializationKeyStack)}" is not the circular source`);
          this.detectCircularReferences(value, serializationKeyStackWithNewKey);
          isCircularValue = false;
        }
        if (isCircularValue) {
          throw new Error(`Circular reference detected:\nCircularly referenced value is value under path "${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`);
        }
      }
    });
  }

  replaceRootStringifyReplacer(toBeStringifiedValue) {
    let serializedObjectCounter = 0;

    return function (key, value) {
      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;
    }
  }
}

Solution 12 - Javascript

I converted the answer of Freddie Nfbnm to TypeScript:

export class JsonUtil {

    static isCyclic(json) {
        const keys = [];
        const stack = [];
        const stackSet = new Set();
        let detected = false;

        function detect(obj, key) {
            if (typeof obj !== 'object') {
                return;
            }

            if (stackSet.has(obj)) { // it's cyclic! Print the object and its locations.
                const oldIndex = stack.indexOf(obj);
                const l1 = keys.join('.') + '.' + key;
                const 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 (const 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(json, 'obj');
        return detected;
    }

}

Solution 13 - Javascript

Just to throw my version into the mix... below is a remix of @dkurzaj 's code (which is itself a remix of @Aaron V 's, @user4976005 's, @Trey Mack 's and finally @Freddie Nfbnm 's [removed?] code) plus @darksinge 's WeakMap idea. So... this thread's Megamix, I guess :)

In my version, a report (rather than console.log'ed entries) is optionally returned as an array of objects. If a report is not required, testing stops on the first sighting of a circular reference (a'la @darksinge 's code).

Further, hasOwnProperty has been removed as Object.keys returns only hasOwnProperty properties (see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys ).

function isCyclic(x, bReturnReport) {
    var a_sKeys = [],
        a_oStack = [],
        wm_oSeenObjects = new WeakMap(), //# see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap
        oReturnVal = {
            found: false,
            report: []
        }
    ;

    //# Setup the recursive logic to locate any circular references while kicking off the initial call
    (function doIsCyclic(oTarget, sKey) {
        var a_sTargetKeys, sCurrentKey, i;

        //# If we've seen this oTarget before, flip our .found to true
        if (wm_oSeenObjects.has(oTarget)) {
            oReturnVal.found = true;

            //# If we are to bReturnReport, add the entries into our .report
            if (bReturnReport) {
                oReturnVal.report.push({
                    instance: oTarget,
                    source: a_sKeys.slice(0, a_oStack.indexOf(oTarget) + 1).join('.'),
                    duplicate: a_sKeys.join('.') + "." + sKey
                });
            }
        }
        //# Else if oTarget is an instanceof Object, determine the a_sTargetKeys and .set our oTarget into the wm_oSeenObjects
        else if (oTarget instanceof Object) {
            a_sTargetKeys = Object.keys(oTarget);
            wm_oSeenObjects.set(oTarget /*, undefined*/);

            //# If we are to bReturnReport, .push the  current level's/call's items onto our stacks
            if (bReturnReport) {
                if (sKey) { a_sKeys.push(sKey) };
                a_oStack.push(oTarget);
            }

            //# Traverse the a_sTargetKeys, pulling each into sCurrentKey as we go
            //#     NOTE: If you want all properties, even non-enumerables, see Object.getOwnPropertyNames() so there is no need to call .hasOwnProperty (per: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys)
            for (i = 0; i < a_sTargetKeys.length; i++) {
                sCurrentKey = a_sTargetKeys[i];

                //# If we've already .found a circular reference and we're not bReturnReport, fall from the loop
                if (oReturnVal.found && !bReturnReport) {
                    break;
                }
                //# Else if the sCurrentKey is an instanceof Object, recurse to test
                else if (oTarget[sCurrentKey] instanceof Object) {
                    doIsCyclic(oTarget[sCurrentKey], sCurrentKey);
                }
            }

            //# .delete our oTarget into the wm_oSeenObjects
            wm_oSeenObjects.delete(oTarget);

            //# If we are to bReturnReport, .pop the current level's/call's items off our stacks
            if (bReturnReport) {
                if (sKey) { a_sKeys.pop() };
                a_oStack.pop();
            }
        }
    }(x, '')); //# doIsCyclic

    return (bReturnReport ? oReturnVal.report : oReturnVal.found);
}

Solution 14 - Javascript

Most of the other answers only show how to detect that an object-tree has a circular-reference -- they don't tell you how to fix those circular references (ie. replacing the circular-reference values with, eg. undefined).

The below is the function I use to replace all circular-references with undefined:

export const specialTypeHandlers_default = [
	// Set and Map are included by default, since JSON.stringify tries (and fails) to serialize them by default
	{type: Set, keys: a=>a.keys(), get: (a, key)=>key, delete: (a, key)=>a.delete(key)},
	{type: Map, keys: a=>a.keys(), get: (a, key)=>a.get(key), delete: (a, key)=>a.set(key, undefined)},
];
export function RemoveCircularLinks(node, specialTypeHandlers = specialTypeHandlers_default, nodeStack_set = new Set()) {
	nodeStack_set.add(node);

	const specialHandler = specialTypeHandlers.find(a=>node instanceof a.type);
	for (const key of specialHandler ? specialHandler.keys(node) : Object.keys(node)) {
		const value = specialHandler ? specialHandler.get(node, key) : node[key];
		// if the value is already part of visited-stack, delete the value (and don't tunnel into it)
		if (nodeStack_set.has(value)) {
			if (specialHandler) specialHandler.delete(node, key);
			else node[key] = undefined;
		}
		// else, tunnel into it, looking for circular-links at deeper levels
		else if (typeof value == "object" && value != null) {
			RemoveCircularLinks(value, specialTypeHandlers, nodeStack_set);
		}
	}

	nodeStack_set.delete(node);
}

For use with JSON.stringify specifically, simply call the function above prior to the stringification (note that it does mutate the passed-in object):

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

Solution 15 - Javascript

if you just need to see the content of that circular object, just use console.table(circularObj)

Solution 16 - Javascript

Try using console.log() on the chrome/firefox browser to identify where the issue encountered.

On Firefox using Firebug plugin, you can debug your javascript line by line.

Update:

Refer below example of circular reference issue and which has been handled:-

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

Refer example LIVE DEMO

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMads Mob&#230;kView Question on Stackoverflow
Solution 1 - JavascriptTrey MackView Answer on Stackoverflow
Solution 2 - JavascriptAaron VView Answer on Stackoverflow
Solution 3 - JavascriptMattb150View Answer on Stackoverflow
Solution 4 - JavascriptThomasView Answer on Stackoverflow
Solution 5 - Javascriptuser4976005View Answer on Stackoverflow
Solution 6 - Javascriptgurvinder372View Answer on Stackoverflow
Solution 7 - JavascriptdarksingeView Answer on Stackoverflow
Solution 8 - JavascriptdkurzajView Answer on Stackoverflow
Solution 9 - JavascriptSzybkiSaszaView Answer on Stackoverflow
Solution 10 - JavascriptJiminPView Answer on Stackoverflow
Solution 11 - JavascriptCarl GView Answer on Stackoverflow
Solution 12 - JavascriptmvermandView Answer on Stackoverflow
Solution 13 - JavascriptCampbelnView Answer on Stackoverflow
Solution 14 - JavascriptVenryxView Answer on Stackoverflow
Solution 15 - JavascriptAndreia EmiliaView Answer on Stackoverflow
Solution 16 - JavascriptSiva CharanView Answer on Stackoverflow