Wie kann ich eine kreisförmige Struktur in einem JSON-ähnlichen Format drucken?


680

Ich habe ein großes Objekt, das ich in JSON konvertieren und senden möchte. Es hat jedoch eine kreisförmige Struktur. Ich möchte alle vorhandenen Zirkelverweise wegwerfen und alles senden, was stringifiziert werden kann. Wie mache ich das?

Vielen Dank.

var obj = {
  a: "foo",
  b: obj
}

Ich möchte obj in folgende Zeichenfolge einteilen:

{"a":"foo"}

5
Könnten Sie bitte ein Beispielobjekt mit einem Zirkelverweis posten, den Sie analysieren möchten?
TWickz

3
so etwas wie dies ?
Alvin Wong


2
Spät zur Party, aber es gibt ein Github- Projekt, um das zu erledigen.
Preston S

Antworten:


605

Verwendung JSON.stringifymit einem benutzerdefinierten Ersatz. Zum Beispiel:

// Demo: Circular reference
var circ = {};
circ.circ = circ;

// Note: cache should not be re-used by repeated calls to JSON.stringify.
var cache = [];
JSON.stringify(circ, (key, value) => {
  if (typeof value === 'object' && value !== null) {
    // Duplicate reference found, discard key
    if (cache.includes(value)) return;

    // Store value in our collection
    cache.push(value);
  }
  return value;
});
cache = null; // Enable garbage collection

Der Ersetzer in diesem Beispiel ist nicht 100% korrekt (abhängig von Ihrer Definition von "Duplikat"). Im folgenden Fall wird ein Wert verworfen:

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, ...);

Das Konzept lautet jedoch: Verwenden Sie einen benutzerdefinierten Ersetzer und verfolgen Sie die analysierten Objektwerte.

Als Dienstprogrammfunktion in es6 geschrieben:

// safely handles circular references
JSON.safeStringify = (obj, indent = 2) => {
  let cache = [];
  const retVal = JSON.stringify(
    obj,
    (key, value) =>
      typeof value === "object" && value !== null
        ? cache.includes(value)
          ? undefined // Duplicate reference found, discard key
          : cache.push(value) && value // Store value in our collection
        : value,
    indent
  );
  cache = null;
  return retVal;
};

// Example:
console.log('options', JSON.safeStringify(options))

1
@ Harry Was ist der Fehler? Ich werde die Antwort gerne korrigieren, wenn es Ungenauigkeiten gibt.
Rob W

1
@CruzDiablo Das Serialisieren von DOM ist normalerweise bedeutungslos. Wenn Sie sich jedoch eine sinnvolle Serialisierungsmethode für Ihre Zwecke Node.prototype.toJSON = function() { return 'whatever you think that is right'; };vorstellen können , können Sie versuchen, DOM-Objekten eine benutzerdefinierte Serialisierungsmethode hinzuzufügen: (Wenn Sie etwas allgemeineres / spezifischeres wünschen, versuchen Sie einfach etwas im Prototypbaum: HTMLDivElement implementiert HTMLElement-Implementierungen Element implementiert Node implementiert EventTarget; Hinweis: Dies kann browserabhängig sein, der vorherige Baum gilt für Chrome)
Rob W

7
Dies ist falsch, da das zweite Auftreten von Objekten, die zweimal enthalten sind, übersprungen wird, auch wenn sie nicht in einer wirklich zyklischen Struktur vorliegen. var a={id:1}; JSON.stringify([a,a]);
user2451227

3
@ user2451227 "Der Ersetzer in diesem Beispiel ist nicht 100% korrekt (abhängig von Ihrer Definition von" Duplikat "). Das Konzept lautet jedoch: Verwenden Sie einen benutzerdefinierten Ersetzer und verfolgen Sie die analysierten Objektwerte."
Rob W

4
Das GC-Anliegen hier ist wohl überflüssig. Wenn dies als einzelnes Skript ausgeführt wird, wird das Skript sofort beendet. Wenn dies in einer Funktion für die Umsetzung gekapselt ist dann cachewird nicht erreichbar sein developer.mozilla.org/en-US/docs/Web/JavaScript/...
Trindaz

704

In Node.js können Sie util.inspect (Objekt) verwenden . Es ersetzt automatisch kreisförmige Links durch "[Rundschreiben]".


Obwohl es integriert ist (keine Installation erforderlich) , müssen Sie es importieren

import * as util from 'util' // has no default export
import { inspect } from 'util' // or directly
// or 
var util = require('util')
Um es zu benutzen, rufen Sie einfach an
console.log(util.inspect(myObject))

Beachten Sie auch, dass Sie das zu inspizierende Optionsobjekt übergeben können (siehe Link oben).

inspect(myObject[, options: {showHidden, depth, colors, showProxy, ...moreOptions}])



Bitte lesen Sie und geben Sie den Kommentatoren unten ein Lob ...


134
util ist ein eingebautes Modul, Sie müssen es nicht installieren.
Mitar

10
console.log (util.inspect (obj))
Starsinmypockets

19
@Mitar es ist eingebaut, aber Sie müssen noch das Modul ladenvar util = require('util');
Bodecker

14
Sei kein Trottel wie ich, es ist einfach obj_str = util.inspect(thing) , NICHT <s> garbage_str = JSON.stringify(util.inspect(thing))</ s>
ThorSummoner

7
Dies ist viel besser, als mit der Überprüfung von Typen herumzuspielen. Warum kann Stringify nicht einfach so funktionieren? Wenn es weiß, dass es einen Zirkelverweis gibt, warum kann man ihm nicht einfach sagen, dass er ihn ignorieren soll?
Chris Peacock

141

Ich frage mich, warum noch niemand die richtige Lösung von der MDN-Seite gepostet hat ...

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

Gesehene Werte sollten in einer Menge gespeichert werden , nicht in einem Array (der Ersetzer wird für jedes Element aufgerufen ), und es ist nicht erforderlich, JSON.stringify jedes Element in der Kette zu testen , was zu einer Zirkelreferenz führt .

Wie in der akzeptierten Antwort entfernt diese Lösung alle sich wiederholenden Werte , nicht nur die kreisförmigen. Aber zumindest hat es keine exponentielle Komplexität.


Ordentlich, aber das ist nur ES2015. Keine IE-Unterstützung.
Martin Capodici

43
Yoda sagt: "Wenn IE noch unterstützt wird, dann sollte man einen Transpiler verwenden, den man sollte."
Spanien Zug

1
replacer = () => { const seen = new WeakSet(); return (key, value) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { return; } seen.add(value); } return value; }; } () => { const seen = new WeakSet(); return (key, value) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { return; } seen.add(value); … JSON.stringify({a:1, b: '2'}, replacer)kehrt undefinedin Chrom zurück
Roberto Tomás

1
Es funktioniert in React + Typescript. danke
user3417479

76

mach einfach

npm i --save circular-json

dann in Ihrer js-Datei

const CircularJSON = require('circular-json');
...
const json = CircularJSON.stringify(obj);

https://github.com/WebReflection/circular-json

HINWEIS: Ich habe nichts mit diesem Paket zu tun. Aber ich benutze es dafür.

Update 2020

Bitte beachten Sie, dass CircularJSON nur in Wartung ist und sein Nachfolger abgeflacht ist.


Vielen Dank! Tolle Bibliothek, viel Zeit gespart. Super winzig (nur 1,4 KB minimiert).
Brian Haak

16
Ich denke, Sie benötigen möglicherweise mehr Begründung für die Verwendung eines Moduls als "nur". Und es ist nicht toll, JSONgrundsätzlich zu überschreiben .
Edwin

Ich musste ein Objekt kopieren, um es für Stub-Tests zu verwenden. Diese Antwort war perfekt. Ich habe das Objekt kopiert und dann die Überschreibung entfernt. Vielen Dank!!
Chris Sharp

1
Nach Angaben des Autors ist dieses Paket veraltet. CircularJSON ist nur in Wartung, abgeflacht ist sein Nachfolger. Link: github.com/WebReflection/flatted#flatted
Robert Molina

3
Beachten Sie, dass das Paket 'flatted' (und Circular-Json?) Die JSON.stringify () -Funktionalität nicht repliziert. Es erstellt ein eigenes Nicht-JSON-Format. (z. B. Flatted.stringify({blah: 1})Ergebnisse in [{"blah":1}]) Ich sehe, dass jemand versucht hat, ein Problem zu diesem Thema anzusprechen, und der Autor hat sie beschimpft und das Problem für Kommentare gesperrt.
Jameslol

48

Die Lösung von Trindaz hat mir sehr gut gefallen - ausführlicher, aber es gab einige Fehler. Ich habe sie für jeden repariert, der es auch mag.

Außerdem habe ich meinen Cache-Objekten eine Längenbeschränkung hinzugefügt.

Wenn das Objekt, das ich drucke, wirklich groß ist - ich meine unendlich groß - möchte ich meinen Algorithmus einschränken.

JSON.stringifyOnce = function(obj, replacer, indent){
    var printedObjects = [];
    var printedObjectKeys = [];

    function printOnceReplacer(key, value){
        if ( printedObjects.length > 2000){ // browsers will not print more than 20K, I don't see the point to allow 2K.. algorithm will not be fast anyway if we have too many objects
        return 'object too long';
        }
        var printedObjIndex = false;
        printedObjects.forEach(function(obj, index){
            if(obj===value){
                printedObjIndex = index;
            }
        });

        if ( key == ''){ //root element
             printedObjects.push(obj);
            printedObjectKeys.push("root");
             return value;
        }

        else if(printedObjIndex+"" != "false" && typeof(value)=="object"){
            if ( printedObjectKeys[printedObjIndex] == "root"){
                return "(pointer to root)";
            }else{
                return "(see " + ((!!value && !!value.constructor) ? value.constructor.name.toLowerCase()  : typeof(value)) + " with key " + printedObjectKeys[printedObjIndex] + ")";
            }
        }else{

            var qualifiedKey = key || "(empty key)";
            printedObjects.push(value);
            printedObjectKeys.push(qualifiedKey);
            if(replacer){
                return replacer(key, value);
            }else{
                return value;
            }
        }
    }
    return JSON.stringify(obj, printOnceReplacer, indent);
};

In dieser Zeile fehlt eine Nullprüfung: return "(siehe" + (!! value.constructor? Value.constructor.name.toLowerCase (): typeof (value)) + "with key" + gedruckteObjectKeys [gedruckterObjIndex] + ")";
Isak

Ich werde es gerne hinzufügen. Lassen Sie mich einfach wissen, was nullbar ist, da ich bisher Probleme hatte.
Kerl Mograbi

2
// Browser drucken nicht mehr als 20 KB - Sie setzen jedoch das Limit auf 2 KB. Vielleicht für die Zukunft ändern?
Pochen

38

@ RobWs Antwort ist richtig, aber das ist performanter! Weil es eine Hashmap / Set verwendet:

const customStringify = function (v) {
  const cache = new Set();
  return JSON.stringify(v, function (key, value) {
    if (typeof value === 'object' && value !== null) {
      if (cache.has(value)) {
        // Circular reference found
        try {
          // If this value does not reference a parent it can be deduped
         return JSON.parse(JSON.stringify(value));
        }
        catch (err) {
          // discard key if value cannot be deduped
         return;
        }
      }
      // Store value in our set
      cache.add(value);
    }
    return value;
  });
};

Versuchen Sie für tief verschachtelte Objekte mit Zirkelverweisen stringifyDeep => github.com/ORESoftware/safe-stringify
Alexander Mills

Es ist möglich, dass die Set-Implementierung nur ein Array und indexOf unter der Haube verwendet, aber das habe ich nicht bestätigt.
Alexander Mills

Dies ist das Entfernen von übergeordneten Knoten mit untergeordneten Knoten, auch mit unterschiedlichen Werten - z. B. - {"a":{"b":{"a":"d"}}}und sogar das Entfernen von Knoten mit leerem Objekt {}
Sandip Pingle

Können Sie ein Beispiel für diesen Sandip zeigen? Erstellen Sie eine gist.github.com oder so weiter
Alexander Mills

Ausgezeichnet !!! Erste (von oben, aber nur 2-3 Funktionslösungen geprüft) Arbeitslösung hier unter node.js und Fission ;-) - Bibliotheken aufgehängt.
Tom

37

Beachten Sie, dass es auch eine JSON.decyclevon Douglas Crockford implementierte Methode gibt . Siehe seine cycle.js . Auf diese Weise können Sie nahezu jede Standardstruktur stringifizieren:

var a = [];
a[0] = a;
a[1] = 123;
console.log(JSON.stringify(JSON.decycle(a)));
// result: '[{"$ref":"$"},123]'.

Sie können das ursprüngliche Objekt auch mit der retrocycleMethode neu erstellen . Sie müssen also keine Zyklen von Objekten entfernen, um sie zu stringifizieren.

Dies funktioniert jedoch nicht für DOM-Knoten (die typische Ursachen für Zyklen in realen Anwendungsfällen sind). Zum Beispiel wird dies werfen:

var a = [document.body];
console.log(JSON.stringify(JSON.decycle(a)));

Ich habe eine Gabel gemacht, um dieses Problem zu lösen (siehe meine Gabel cycle.js ). Dies sollte gut funktionieren:

var a = [document.body];
console.log(JSON.stringify(JSON.decycle(a, true)));

Beachten Sie, dass in meiner Gabelung JSON.decycle(variable)wie im Original funktioniert und eine Ausnahme variableausgelöst wird, wenn die DOM-Knoten / Elemente enthalten.

Wenn Sie verwenden JSON.decycle(variable, true), akzeptieren Sie die Tatsache, dass das Ergebnis nicht umkehrbar ist (Retrocycle erstellt keine DOM-Knoten neu). DOM-Elemente sollten jedoch bis zu einem gewissen Grad identifizierbar sein. Wenn ein divElement beispielsweise eine ID hat, wird es durch eine Zeichenfolge ersetzt "div#id-of-the-element".


2
Sowohl sein als auch Ihr Code geben mir einen "RangeError: Maximale Aufrufstapelgröße überschritten", wenn ich sie verwende.
JCollum

Ich kann einen Blick darauf werfen, ob Sie Ihren Code auf der Geige bereitstellen oder ein Problem auf Github hinzufügen: github.com/Eccenux/JSON-js/issues
Nux

Das habe ich gesucht. JSON.decycle(a, true)Was passiert, wenn Sie true als Parameter für die Decycle-Funktion übergeben?
Rudra

@Rudra true macht die stringifyNodesOption in der Gabel wahr. Dies wird zB divmit id = "some-id" in string : div#some-id. Sie werden einige Probleme vermeiden, aber Sie werden nicht in der Lage sein, einen vollständigen Rückzyklus durchzuführen.
Nux

Es gibt npm Paket npmjs.com/package/json-js , aber es wurde für eine Weile nicht aktualisiert
Michael Freidgeim

23

Ich würde empfehlen, json-stringify-safe von @ isaacs zu überprüfen - es wird in NPM verwendet.

Übrigens: Wenn Sie Node.js nicht verwenden, können Sie einfach die Zeilen 4 bis 27 aus dem entsprechenden Teil des Quellcodes kopieren und einfügen .

Installieren:

$ npm install json-stringify-safe --save

Benutzen:

// Require the thing
var stringify = require('json-stringify-safe');

// Take some nasty circular object
var theBigNasty = {
  a: "foo",
  b: theBigNasty
};

// Then clean it up a little bit
var sanitized = JSON.parse(stringify(theBigNasty));

Dies ergibt:

{
  a: 'foo',
  b: '[Circular]'
}

Beachten Sie, dass Sie genau wie bei der erwähnten Vanilla JSON.stringify-Funktion wie @Rob W auch das Desinfektionsverhalten anpassen können, indem Sie eine "Ersetzer" -Funktion als zweites Argument an übergeben stringify(). Wenn Sie sich um ein einfaches Beispiel zu finden , wie dies zu tun, schrieb ich nur eine benutzerdefinierte replacer die Fehler, regexps und Funktionen in für Menschen lesbaren Strings coerces hier .


13

Für zukünftige Googler, die nach einer Lösung für dieses Problem suchen, wenn Sie die Schlüssel aller Zirkelverweise nicht kennen, können Sie einen Wrapper um die Funktion JSON.stringify verwenden, um Zirkelverweise auszuschließen. Ein Beispielskript finden Sie unter https://gist.github.com/4653128 .

Die Lösung besteht im Wesentlichen darin, einen Verweis auf zuvor gedruckte Objekte in einem Array beizubehalten und diesen in einer Ersetzungsfunktion zu überprüfen, bevor ein Wert zurückgegeben wird. Es ist einschränkender, als nur Zirkelverweise auszuschließen, da es auch ausschließt, dass ein Objekt jemals zweimal gedruckt wird. Eine der Nebeneffekte besteht darin, Zirkelverweise zu vermeiden.

Beispiel Wrapper:

function stringifyOnce(obj, replacer, indent){
    var printedObjects = [];
    var printedObjectKeys = [];

    function printOnceReplacer(key, value){
        var printedObjIndex = false;
        printedObjects.forEach(function(obj, index){
            if(obj===value){
                printedObjIndex = index;
            }
        });

        if(printedObjIndex && typeof(value)=="object"){
            return "(see " + value.constructor.name.toLowerCase() + " with key " + printedObjectKeys[printedObjIndex] + ")";
        }else{
            var qualifiedKey = key || "(empty key)";
            printedObjects.push(value);
            printedObjectKeys.push(qualifiedKey);
            if(replacer){
                return replacer(key, value);
            }else{
                return value;
            }
        }
    }
    return JSON.stringify(obj, printOnceReplacer, indent);
}

3
Schöner Code. Sie haben jedoch einen dummen Fehler, Sie schreiben, if(printedObjIndex)während Sie schreiben sollten, if(printedObjIndex==false)da indexdies auch der Fall sein kann, in 0den übersetzt wird, falsesofern Sie nicht ausdrücklich etwas anderes angeben.
Kerl Mograbi

1
@guymograbi Meinst du nicht ===? 0 == falseist true, 0 === falseist false. ; ^) Aber ich würde lieber nicht printedObjIndexmit false initialisieren , da du dann dagegen prüfen kannst, undefineddass du (na ja, Trindaz) Metaphern nicht so seltsam mischst.
Ruffin

@ Ruffin schöner Fang. Ja, natürlich, benutze immer harte Gleichheit und Jshint, um solche dummen Fehler zu fangen.
Kerl Mograbi

4

Verwenden Sie die JSON.stringify-Methode mit einem Ersetzer. Lesen Sie diese Dokumentation für weitere Informationen. http://msdn.microsoft.com/en-us/library/cc836459%28v=vs.94%29.aspx

var obj = {
  a: "foo",
  b: obj
}

var replacement = {"b":undefined};

alert(JSON.stringify(obj,replacement));

Finden Sie heraus, wie Sie das Ersatzarray mit zyklischen Referenzen füllen können. Mit der typeof-Methode können Sie feststellen, ob die Eigenschaft vom Typ 'object' (Referenz) ist, und mit einer genauen Gleichheitsprüfung (===) die Zirkelreferenz überprüfen.


4
Dies funktioniert möglicherweise nur im IE (unter Berücksichtigung der Tatsache, dass MSDN eine Dokumentation von Microsoft ist und Microsoft den IE erstellt). In Firefox / Chrome generiert jsfiddle.net/ppmaW den Zirkelreferenzfehler . Zur Info: var obj = {foo:obj}nicht nicht einen zirkulären Verweis erstellen. Stattdessen wird ein Objekt erstellt, dessen fooAttribut auf den vorherigen Wert von verweist obj( undefinedfalls nicht zuvor definiert, deklariert wegen var obj).
Rob W

4

Wenn

console.log(JSON.stringify(object));

führt zu a

TypeError: zyklischer Objektwert

Dann möchten Sie vielleicht so drucken:

var output = '';
for (property in object) {
  output += property + ': ' + object[property]+'; ';
}
console.log(output);

21
Vielleicht, weil es nur eine Ebene druckt?
Alex Turpin

SEHR EINFACH Ich habe dies positiv bewertet, weil es für mich sofort in Chrom funktioniert hat. EXCELLENT
Liebe und Frieden - Joe Codeswell

4
var a={b:"b"};
a.a=a;
JSON.stringify(preventCircularJson(a));

bewertet zu:

"{"b":"b","a":"CIRCULAR_REFERENCE_REMOVED"}"

mit der Funktion:

/**
 * Traverses a javascript object, and deletes all circular values
 * @param source object to remove circular references from
 * @param censoredMessage optional: what to put instead of censored values
 * @param censorTheseItems should be kept null, used in recursion
 * @returns {undefined}
 */
function preventCircularJson(source, censoredMessage, censorTheseItems) {
    //init recursive value if this is the first call
    censorTheseItems = censorTheseItems || [source];
    //default if none is specified
    censoredMessage = censoredMessage || "CIRCULAR_REFERENCE_REMOVED";
    //values that have allready apeared will be placed here:
    var recursiveItems = {};
    //initaite a censored clone to return back
    var ret = {};
    //traverse the object:
    for (var key in source) {
        var value = source[key]
        if (typeof value == "object") {
            //re-examine all complex children again later:
            recursiveItems[key] = value;
        } else {
            //simple values copied as is
            ret[key] = value;
        }
    }
    //create list of values to censor:
    var censorChildItems = [];
    for (var key in recursiveItems) {
        var value = source[key];
        //all complex child objects should not apear again in children:
        censorChildItems.push(value);
    }
    //censor all circular values
    for (var key in recursiveItems) {
        var value = source[key];
        var censored = false;
        censorTheseItems.forEach(function (item) {
            if (item === value) {
                censored = true;
            }
        });
        if (censored) {
            //change circular values to this
            value = censoredMessage;
        } else {
            //recursion:
            value = preventCircularJson(value, censoredMessage, censorChildItems.concat(censorTheseItems));
        }
        ret[key] = value

    }

    return ret;
}

3

Ich weiß, dass dies eine alte Frage ist, aber ich möchte ein von mir erstelltes NPM-Paket namens Smart-Circular vorschlagen , das anders funktioniert als die anderen vorgeschlagenen Methoden. Dies ist besonders nützlich, wenn Sie große und tiefe Objekte verwenden .

Einige Funktionen sind:

  • Ersetzen von Zirkelreferenzen oder einfach wiederholten Strukturen innerhalb des Objekts durch den Pfad, der zu seinem ersten Auftreten führt (nicht nur die Zeichenfolge [Zirkel] );

  • Durch die Suche nach Kreisförmigkeiten in einer Breitensuche stellt das Paket sicher, dass dieser Pfad so klein wie möglich ist. Dies ist wichtig, wenn es sich um sehr große und tiefe Objekte handelt, bei denen die Pfade ärgerlich lang und schwer zu verfolgen sein können (der benutzerdefinierte Ersatz in) JSON.stringify führt eine DFS durch);

  • Ermöglicht personalisierte Ersetzungen, um weniger wichtige Teile des Objekts zu vereinfachen oder zu ignorieren.

  • Schließlich werden die Pfade genau so geschrieben, wie es für den Zugriff auf das angegebene Feld erforderlich ist. Dies kann Ihnen beim Debuggen helfen.


3

Das zweite Argument für JSON.stringify () auch ermöglicht es Ihnen , eine Reihe von Schlüsselnamen angeben , die von jedem Objekt erhalten werden soll es innerhalb Ihrer Daten trifft. Dies funktioniert möglicherweise nicht für alle Anwendungsfälle, ist jedoch eine viel einfachere Lösung.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify

var obj = {
    a: "foo",
    b: this
}

var json = JSON.stringify(obj, ['a']);
console.log(json);
// {"a":"foo"}

Hinweis: Seltsamerweise löst die Objektdefinition von OP in der neuesten Version von Chrome oder Firefox keinen Zirkelreferenzfehler aus. Die Definition in dieser Antwort wurde so modifiziert , dass es tat wirft einen Fehler.



Diese Antwort sollte akzeptiert werden
Manische Depression

2

Verwenden circular-jsonSie nicht (es ist veraltet), um die Antwort auf das Überschreiben der Funktionsweise von JSON zu aktualisieren (wahrscheinlich nicht empfohlen, aber sehr einfach ). Verwenden Sie stattdessen den abgeflachten Nachfolger:

https://www.npmjs.com/package/flatted

Aus der alten Antwort oben von @ user1541685 entlehnt, aber durch die neue ersetzt:

npm i --save flatted

dann in Ihrer js-Datei

const CircularJSON = require('flatted');
const json = CircularJSON.stringify(obj);

1

Ich habe auf github eine Circular-Json-Bibliothek gefunden , die für mein Problem gut funktioniert hat.

Einige gute Funktionen, die ich nützlich fand:

  • Unterstützt die Verwendung auf mehreren Plattformen, aber ich habe es bisher nur mit node.js getestet.
  • Die API ist dieselbe, Sie müssen sie also nur einschließen und als JSON-Ersatz verwenden.
  • Es verfügt über eine eigene Analysemethode, mit der Sie die "zirkulären" serialisierten Daten wieder in ein Objekt konvertieren können.

2
Diese Bibliothek hat einen Fehler für mich ausgelöst, daher muss ich nach einem anderen suchen. ERROR TypeError: toISOString ist keine Funktion bei String.toJSON (<anonymous>) bei Object. <Anonymous> ( localhost: 8100 / build / polyfills.js: 1: 3458 ) bei JSON.stringify (<anonymous>) bei Object. stringifyRecursion [als stringify] ( localhost: 8100 / build / main.js: 258450: 15 )
Mark Ellul

1
@MarkEllul Ich habe den Kommentar im Jahr 2015 geschrieben und wenn ich eine bessere Alternative sehe, werde ich ihn hier mit einer Bearbeitung veröffentlichen. Ich habe immer noch gelegentlich das gleiche Problem in der täglichen Arbeit und bevorzuge normalerweise meine eigenen manuellen Funktionen rekursiv mit einer ordnungsgemäßen / sicheren Inspektion. Ich würde vorschlagen, funktionale Programmierpraktiken zu überprüfen, wenn Sie nicht vertraut sind. Normalerweise erleichtert dies diese Art von rekursiven Operationen, da sie weniger schwierig und zuverlässiger sind.
JacopKane

Außerdem wird "toISOString ist keine Funktion" beim Versuch, ein Ereignis zu stringifizieren und in einem Zypressentest erneut zu senden
Devin G Rhode

1

Ich löse dieses Problem folgendermaßen:

var util = require('util');

// Our circular object
var obj = {foo: {bar: null}, a:{a:{a:{a:{a:{a:{a:{hi: 'Yo!'}}}}}}}};
obj.foo.bar = obj;

// Generate almost valid JS object definition code (typeof string)
var str = util.inspect(b, {depth: null});

// Fix code to the valid state (in this example it is not required, but my object was huge and complex, and I needed this for my case)
str = str
    .replace(/<Buffer[ \w\.]+>/ig, '"buffer"')
    .replace(/\[Function]/ig, 'function(){}')
    .replace(/\[Circular]/ig, '"Circular"')
    .replace(/\{ \[Function: ([\w]+)]/ig, '{ $1: function $1 () {},')
    .replace(/\[Function: ([\w]+)]/ig, 'function $1(){}')
    .replace(/(\w+): ([\w :]+GMT\+[\w \(\)]+),/ig, '$1: new Date("$2"),')
    .replace(/(\S+): ,/ig, '$1: null,');

// Create function to eval stringifyed code
var foo = new Function('return ' + str + ';');

// And have fun
console.log(JSON.stringify(foo(), null, 4));

Das hat bei mir ziemlich gut funktioniert, aber es scheint, als würden Klassen so dargestellt _class: ClassName { data: "here" }, also habe ich die folgende Regel hinzugefügt .replace(/(\w+) {/g, '{ __ClassName__: "$1", '). In meinem Fall habe ich versucht zu sehen, wie ein http-Anforderungsobjekt aussieht.
Redbmk

1

Ich weiß, dass diese Frage alt ist und viele gute Antworten hat, aber ich poste diese Antwort wegen ihres neuen Geschmacks (es5 +).


1

Obwohl dies ausreichend beantwortet wurde, können Sie die betreffende Eigenschaft auch explizit löschen, bevor Sie sie mit dem deleteOperator stringifizieren .

delete obj.b; 
const jsonObject = JSON.stringify(obj);

Operator löschen

Dadurch entfällt die Notwendigkeit, komplexe Logik zum Entfernen von Zirkelverweisen zu erstellen oder zu verwalten.


1
function myStringify(obj, maxDeepLevel = 2) {
  if (obj === null) {
    return 'null';
  }
  if (obj === undefined) {
    return 'undefined';
  }
  if (maxDeepLevel < 0 || typeof obj !== 'object') {
    return obj.toString();
  }
  return Object
    .entries(obj)
    .map(x => x[0] + ': ' + myStringify(x[1], maxDeepLevel - 1))
    .join('\r\n');
}


0

Basierend auf den anderen Antworten erhalte ich den folgenden Code. Es funktioniert ziemlich gut mit Zirkelverweisen, Objekten mit benutzerdefinierten Konstruktoren.

Von dem gegebenen Objekt, das serialisiert werden soll,

  • Zwischenspeichern Sie alle Objekte, auf die Sie beim Durchlaufen des Objekts stoßen, und weisen Sie jedem Objekt eine eindeutige Hash-ID zu (eine automatisch inkrementierende Nummer funktioniert auch).
  • Sobald eine Zirkelreferenz gefunden wurde, markieren Sie dieses Feld im neuen Objekt als Zirkel und speichern Sie die Hash-ID des ursprünglichen Objekts als Attribut.

Github Link - DecycledJSON

DJSHelper = {};
DJSHelper.Cache = [];
DJSHelper.currentHashID = 0;
DJSHelper.ReviveCache = [];

// DOES NOT SERIALIZE FUNCTION
function DJSNode(name, object, isRoot){
    this.name = name;
    // [ATTRIBUTES] contains the primitive fields of the Node
    this.attributes = {};

    // [CHILDREN] contains the Object/Typed fields of the Node
    // All [CHILDREN] must be of type [DJSNode]
    this.children = []; //Array of DJSNodes only

    // If [IS-ROOT] is true reset the Cache and currentHashId
    // before encoding
    isRoot = typeof isRoot === 'undefined'? true:isRoot;
    this.isRoot = isRoot;
    if(isRoot){
        DJSHelper.Cache = [];
        DJSHelper.currentHashID = 0;

        // CACHE THE ROOT
        object.hashID = DJSHelper.currentHashID++;
        DJSHelper.Cache.push(object);
    }

    for(var a in object){
        if(object.hasOwnProperty(a)){
            var val = object[a];

            if (typeof val === 'object') {
                // IF OBJECT OR NULL REF.

                /***************************************************************************/
                // DO NOT REMOVE THE [FALSE] AS THAT WOULD RESET THE [DJSHELPER.CACHE]
                // AND THE RESULT WOULD BE STACK OVERFLOW
                /***************************************************************************/
                if(val !== null) {
                    if (DJSHelper.Cache.indexOf(val) === -1) {
                        // VAL NOT IN CACHE
                        // ADD THE VAL TO CACHE FIRST -> BEFORE DOING RECURSION
                        val.hashID = DJSHelper.currentHashID++;
                        //console.log("Assigned", val.hashID, "to", a);
                        DJSHelper.Cache.push(val);

                        if (!(val instanceof Array)) {
                            // VAL NOT AN [ARRAY]
                            try {
                                this.children.push(new DJSNode(a, val, false));
                            } catch (err) {
                                console.log(err.message, a);
                                throw err;
                            }
                        } else {
                            // VAL IS AN [ARRAY]
                            var node = new DJSNode(a, {
                                array: true,
                                hashID: val.hashID // HashID of array
                            }, false);
                            val.forEach(function (elem, index) {
                                node.children.push(new DJSNode("elem", {val: elem}, false));
                            });
                            this.children.push(node);
                        }
                    } else {
                        // VAL IN CACHE
                        // ADD A CYCLIC NODE WITH HASH-ID
                        this.children.push(new DJSNode(a, {
                            cyclic: true,
                            hashID: val.hashID
                        }, false));
                    }
                }else{
                    // PUT NULL AS AN ATTRIBUTE
                    this.attributes[a] = 'null';
                }
            } else if (typeof val !== 'function') {
                // MUST BE A PRIMITIVE
                // ADD IT AS AN ATTRIBUTE
                this.attributes[a] = val;
            }
        }
    }

    if(isRoot){
        DJSHelper.Cache = null;
    }
    this.constructorName = object.constructor.name;
}
DJSNode.Revive = function (xmlNode, isRoot) {
    // Default value of [isRoot] is True
    isRoot = typeof isRoot === 'undefined'?true: isRoot;
    var root;
    if(isRoot){
        DJSHelper.ReviveCache = []; //Garbage Collect
    }
    if(window[xmlNode.constructorName].toString().indexOf('[native code]') > -1 ) {
        // yep, native in the browser
        if(xmlNode.constructorName == 'Object'){
            root = {};
        }else{
            return null;
        }
    }else {
        eval('root = new ' + xmlNode.constructorName + "()");
    }

    //CACHE ROOT INTO REVIVE-CACHE
    DJSHelper.ReviveCache[xmlNode.attributes.hashID] = root;

    for(var k in xmlNode.attributes){
        // PRIMITIVE OR NULL REF FIELDS
        if(xmlNode.attributes.hasOwnProperty(k)) {
            var a = xmlNode.attributes[k];
            if(a == 'null'){
                root[k] = null;
            }else {
                root[k] = a;
            }
        }
    }

    xmlNode.children.forEach(function (value) {
        // Each children is an [DJSNode]
        // [Array]s are stored as [DJSNode] with an positive Array attribute
        // So is value

        if(value.attributes.array){
            // ITS AN [ARRAY]
            root[value.name] = [];
            value.children.forEach(function (elem) {
                root[value.name].push(elem.attributes.val);
            });
            //console.log("Caching", value.attributes.hashID);
            DJSHelper.ReviveCache[value.attributes.hashID] = root[value.name];
        }else if(!value.attributes.cyclic){
            // ITS AN [OBJECT]
            root[value.name] = DJSNode.Revive(value, false);
            //console.log("Caching", value.attributes.hashID);
            DJSHelper.ReviveCache[value.attributes.hashID] = root[value.name];
        }
    });

    // [SEPARATE ITERATION] TO MAKE SURE ALL POSSIBLE
    // [CYCLIC] REFERENCES ARE CACHED PROPERLY
    xmlNode.children.forEach(function (value) {
        // Each children is an [DJSNode]
        // [Array]s are stored as [DJSNode] with an positive Array attribute
        // So is value

        if(value.attributes.cyclic){
            // ITS AND [CYCLIC] REFERENCE
            root[value.name] = DJSHelper.ReviveCache[value.attributes.hashID];
        }
    });

    if(isRoot){
        DJSHelper.ReviveCache = null; //Garbage Collect
    }
    return root;
};

DecycledJSON = {};
DecycledJSON.stringify = function (obj) {
    return JSON.stringify(new DJSNode("root", obj));
};
DecycledJSON.parse = function (json, replacerObject) {
    // use the replacerObject to get the null values
    return DJSNode.Revive(JSON.parse(json));
};
DJS = DecycledJSON;

Beispiel Verwendung 1:

var obj = {
    id:201,
    box: {
        owner: null,
        key: 'storm'
    },
    lines:[
        'item1',
        23
    ]
};

console.log(obj); // ORIGINAL

// SERIALIZE AND THEN PARSE
var jsonObj = DJS.stringify(obj);
console.log(DJS.parse(jsonObj));

Beispiel Verwendung 2:

// PERSON OBJECT

function Person() {
    this.name = null;
    this.child = null;
    this.dad = null;
    this.mom = null;
}
var Dad = new Person();
Dad.name = 'John';
var Mom = new Person();
Mom.name = 'Sarah';
var Child = new Person();
Child.name = 'Kiddo';

Dad.child = Mom.child = Child;
Child.dad = Dad;
Child.mom = Mom;

console.log(Child); // ORIGINAL

// SERIALIZE AND THEN PARSE
var jsonChild = DJS.stringify(Child);
console.log(DJS.parse(jsonChild));

0

Versuche dies:

var obj = {
    a: "foo",
    b: obj
};

var circular_replacer = (value) => {
    var seen = [];
    if (value != null && typeof value == "object") {
        if (seen.indexOf(value) >= 0) return;
        seen.push(value);
    }
    return value;
};

obj = circular_replacer(obj);

Sollte es nach dem seen.push(value)= -D nicht noch ein paar Codezeilen geben ? Gefällt for (var key in value) {value[key] = circular_replacer(value[key]);}
mir

Von Nur-Code-Antworten wird abgeraten. Klicken Sie auf Bearbeiten und fügen Sie einige Wörter hinzu, die zusammenfassen, wie Ihr Code die Frage beantwortet, oder erklären Sie möglicherweise, wie sich Ihre Antwort von der vorherigen Antwort / den vorherigen Antworten unterscheidet. Aus dem Rückblick
Nick

0

Wenn Sie in meiner Lösung auf einen Zyklus stoßen, heißt es nicht nur "Zyklus" (oder nichts), sondern auch etwas wie foo: Siehe Objekt Nr. 42 oben, und um zu sehen, wo foo auf Sie zeigt, können Sie nach oben scrollen und suchen für Objekt # 42 (jedes Objekt sagt beim Start Objekt # xxx mit einer ganzen Zahl xxx)

Snippet:

(function(){
	"use strict";
	var ignore = [Boolean, Date, Number, RegExp, String];
	function primitive(item){
		if (typeof item === 'object'){
			if (item === null) { return true; }
			for (var i=0; i<ignore.length; i++){
				if (item instanceof ignore[i]) { return true; }
			}
			return false;
		} else {
			return true;
		}
	}
	function infant(value){
		return Array.isArray(value) ? [] : {};
	}
	JSON.decycleIntoForest = function decycleIntoForest(object, replacer) {
		if (typeof replacer !== 'function'){
			replacer = function(x){ return x; }
		}
		object = replacer(object);
		if (primitive(object)) return object;
		var objects = [object];
		var forest  = [infant(object)];
		var bucket  = new WeakMap(); // bucket = inverse of objects 
		bucket.set(object, 0);       // i.e., map object to index in array
		function addToBucket(obj){
			var result = objects.length;
			objects.push(obj);
			bucket.set(obj, result);
			return result;
		}
		function isInBucket(obj){
			return bucket.has(obj);
			// objects[bucket.get(obj)] === obj, iff true is returned
		}
		function processNode(source, target){
			Object.keys(source).forEach(function(key){
				var value = replacer(source[key]);
				if (primitive(value)){
					target[key] = {value: value};
				} else {
					var ptr;
					if (isInBucket(value)){
						ptr = bucket.get(value);
					} else {
						ptr = addToBucket(value);
						var newTree = infant(value);
						forest.push(newTree);
						processNode(value, newTree);
					}
					target[key] = {pointer: ptr};
				}
			});
		}
		processNode(object, forest[0]);
		return forest;
	};
})();
the = document.getElementById('the');
function consoleLog(toBeLogged){
  the.textContent = the.textContent + '\n' + toBeLogged;
}
function show(root){
	var cycleFree = JSON.decycleIntoForest(root);
	var shown = cycleFree.map(function(tree, idx){ return false; });
	var indentIncrement = 4;
	function showItem(nodeSlot, indent, label){
	  leadingSpaces = ' '.repeat(indent);
      leadingSpacesPlus = ' '.repeat(indent + indentIncrement);
	  if (shown[nodeSlot]){
	  consoleLog(leadingSpaces + label + ' ... see above (object #' + nodeSlot + ')');
        } else {
		  consoleLog(leadingSpaces + label + ' object#' + nodeSlot);
		  var tree = cycleFree[nodeSlot];
		  shown[nodeSlot] = true;
		  Object.keys(tree).forEach(function(key){
			var entry = tree[key];
			if ('value' in entry){
			  consoleLog(leadingSpacesPlus + key + ": " + entry.value);
                } else {
					if ('pointer' in entry){
						showItem(entry.pointer, indent+indentIncrement, key);
                    }
                }
			});
        }
    }
	showItem(0, 0, 'root');
}
cities4d = {
	Europe:{
		north:[
			{name:"Stockholm", population:1000000, temp:6},
			{name:"Helsinki", population:650000, temp:7.6}
		],
		south:[
			{name:"Madrid", population:3200000, temp:15},
			{name:"Rome", population:4300000, temp:15}
		]
	},
	America:{
		north:[
			{name:"San Francisco", population:900000, temp:14},
			{name:"Quebec", population:530000, temp:4}
		],
		south:[
			{name:"Rio de Janeiro", population:7500000, temp:24},
			{name:"Santiago", population:6300000, temp:14}
		]
	},
	Asia:{
		north:[
			{name:"Moscow", population:13200000, temp:6}
		]
	}
};
cities4d.Europe.north[0].alsoStartsWithS = cities4d.America.north[0];
cities4d.Europe.north[0].smaller = cities4d.Europe.north[1];
cities4d.Europe.south[1].sameLanguage = cities4d.America.south[1];
cities4d.Asia.ptrToRoot = cities4d;
show(cities4d)
<pre id="the"></pre>

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.