Cirkelverwijzingen in JavaScript detecteren en corrigeren

Aangezien ik een kringverwijzing heb in een groot JavaScript-object

En ik probeer JSON.stringify(problematicObject)

En de browser gooit

“TypeError: circulaire structuur converteren naar JSON”

(wat wordt verwacht)

Dus ik wil de oorzaak van deze kringverwijzing vinden, bij voorkeur met behulp van Chrome-ontwikkelaarstools? Is dit mogelijk? Hoe vind en herstel je kringverwijzingen in een groot object?


Antwoord 1, autoriteit 100%

Getrokken van http://blog.vjeux.com/2011/javascript/cyclic-object -detectie.html. Eén regel toegevoegd om te detecteren waar de cyclus is. Plak dit in de Chrome-ontwikkeltools:

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

Dit is de test:

> a = {}
> b = {}
> a.b = b; b.a = a;
> isCyclic(a)
  Object {a: Object}
   "cycle at a"
  Object {b: Object}
   "cycle at b"
  true

Antwoord 2, autoriteit 84%

@tmack’s antwoord is zeker wat ik zocht toen ik deze vraag vond!

Helaas retourneert het veel valse positieven – het retourneert waar als een object wordt gerepliceerd in de JSON, wat niet hetzelfde isals circulariteit. Circulariteit betekent dat een object zijn eigen kind is, b.v.

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

Ik heb het oorspronkelijke antwoord aangepast en dit werkt voor mij:

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

Hier zijn een paar zeer eenvoudige 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

Antwoord 3, autoriteit 16%

Hier is de benadering van MDN voor het detecteren en corrigeren van circulaire verwijzingen bij gebruik van JSON.stringify()op circulaire objecten: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value:

In een cirkelvormige structuur zoals de volgende

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

JSON.stringify()zal mislukken:

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

Om cirkelvormige referenties te serialiseren, kunt u een bibliotheek gebruiken die hen ondersteunt (bijv. Cyclus.js ) of implementeer een oplossing alleen, die de cyclische referenties van de serialiseerbare waarden (/ p>moet vinden en vervangen (of verwijderen).

Het onderstaande fragment illustreert hoe te vinden en filteren (waardoor het gegevensverlies ) een cyclische referentie veroorzaken door De parameter vervangen van 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}

4, Autoriteit 11%

U kunt ook JSON.stringifygebruiken met Try / Catch

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

demo

Toon codefragment


5, Autoriteit 11%

CircularRefaTECREDETOR

Hier is mijn CircularRefaTECREDETOR KLASSE die alle eigenschappenstapel-informatie geeft waar de circulair waarnaar wordt verwezen daadwerkelijk zich bevindt en ook laat zien waar de culprit-referenties zijn.

Dit is vooral handig voor enorme structuren waar het niet voor de hand liggend is door de sleutel welke waarde de bron van de schade is.

Het geeft de circulair gerefereerde waarde stringified af, maar alle verwijzingen naar zichzelf zijn vervangen door “[Circulair object — repareer mij]”.

Gebruik:

CircularReferenceDetector.detectCircularReferences(value);

Opmerking:
Verwijder de Logger.*-instructies als u geen logboekregistratie wilt gebruiken of geen logger beschikbaar hebt.

Technische uitleg:

De recursieve functie doorloopt alle eigenschappen van het object en test of JSON.stringify hierin slaagt of niet.
Als het niet lukt (circulaire verwijzing), dan test het of het lukt door de waarde zelf te vervangen door een constante string. Dit zou betekenen dat als het lukt om deze vervanger te gebruiken, deze waarde de circulair verwezen waarde is. Als dat niet het geval is, gaat het recursief door alle eigenschappen van dat object.

Ondertussen houdt het ook de eigendomsstapel bij om u informatie te geven waar de boosdoener zich bevindt.

Schrijfschrift

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

JavaScript samengesteld uit 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;

Antwoord 6, autoriteit 8%

Hier is een Node ES6-versie gemengd met de antwoorden van @Aaron Ven @user4976005, het lost het probleem op met de aanroep van 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
})

Antwoord 7, autoriteit 8%

Er zijn hier veel antwoorden, maar ik dacht dat ik mijn oplossing aan de mix zou toevoegen. Het is vergelijkbaar met het antwoord van @Trey Mack, maar voor die oplossing is O(n^2) nodig. Deze versie gebruikt WeakMapin plaats van een array, waardoor de tijd tot O(n) wordt verbeterd.

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

En zo ziet het eruit in actie.

> 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

Antwoord 8, autoriteit 3%

Je kunt ook symbolen gebruiken – dankzij die aanpak hoef je geen eigenschappen van het originele object te muteren, behalve het toevoegen van een symbool voor het markeren van een bezocht knooppunt.

Het is schoner en zou sneller moeten zijn dan het verzamelen van node-eigenschappen en vergelijken met het object. Het heeft ook een optionele dieptebeperking als u grote geneste waarden niet wilt serialiseren:

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

Dit resulteert in een object waarvan alle circulaire afhankelijkheden zijn verwijderd en dat ook niet dieper gaat dan gegeven MAX_CLEANUP_DEPTH.

Het gebruik van symbolen is veilig zolang u geen meta-programmering op het object uitvoert – ze zijn transparant en ze zijn niet opsombaar, dus – ze worden niet weergegeven in standaardbewerkingen op het object.

>

Bovendien heeft het retourneren van een nieuw, opgeschoond object het voordeel dat het het originele object niet muteert als u er aanvullende bewerkingen op moet uitvoeren.

Als u geen CIRCULAR-markering wilt, kunt u de code een beetje wijzigen, dus het object overslaan voordat u er daadwerkelijk bewerkingen op uitvoert (in de lus):

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

Antwoord 9

Ik heb dit zojuist gemaakt. Het is misschien vies, maar het werkt toch… 😛

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

Dan

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

Zegt

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

Dit geeft aan dat c.x[3] gelijk is aan c, en c.x = c.y[0].

Of een kleine aanpassing aan deze functie kan je vertellen wat je nodig hebt…

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

Antwoord 10

Hier is @Thomas’ antwoordaangepast voor 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;
    }
  }
}

Antwoord 11

Ik heb het antwoord van Freddie Nfbnm omgezet in 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;
    }
}

Antwoord 12

Om mijn versie in de mix te gooien… hieronder is een remix van @dkurzaj’s code(die is zelf een remix van @Aaron V’s, @user4976005’s, @Trey Mack’s en tenslotte @Freddie Nfbnm’s [verwijderd?] code) plus @darksinge’s WeakMapidee. Dus… deze thread is Megamix, denk ik 🙂

In mijn versie wordt optioneel een rapport (in plaats van console.log-items) geretourneerd als een array van objecten. Als een rapport niet vereist is, stopt het testen bij de eerste waarneming van een kringverwijzing (a’la @darksinge’s code).

Verder is hasOwnPropertyverwijderd omdat Object.keysalleen hasOwnProperty-eigenschappen retourneert (zie: https://developer.mozilla.org/en-US/docs/Web/JavaScript /Referentie/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);
}

Antwoord 13

De meeste andere antwoorden laten alleen zien hoe u detecteertdat een object-boom een ​​circulaire-referentie heeft — ze vertellen u niet hoe u die circulaire repareertreferenties (d.w.z. de circulaire-referentiewaarden vervangen door bijv. undefined).

Het onderstaande is de functie die ik gebruik om alle circulaire verwijzingen te vervangen door 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);
}

Voor specifiek gebruik met JSON.stringify, roept u gewoon de functie hierboven aan voorafgaand aan de stringificatie (merk op dat het het doorgegeven object muteert):

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

Antwoord 14

als je alleen de inhoud van dat ronde object wilt zien, gebruik dan console.table(circularObj)


Antwoord 15

Probeer console.log()in de chrome/firefox-browser te gebruiken om te bepalen waar het probleem is opgetreden.

Op Firefox met behulp van Firebug-plug-in kunt u uw JavaScript-lijn per regel debuggen.

UPDATE:

Verwijs hieronder voorbeeld van circulaire referentieprobleem en dat is afgehandeld: –

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

Zie voorbeeld live demo

Other episodes