Serialisierungsobjekt, das einen zyklischen Objektwert enthält


151

Ich habe ein Objekt (Analysebaum), das untergeordnete Knoten enthält, die Verweise auf andere Knoten sind.

Ich möchte dieses Objekt mit serialisieren JSON.stringify(), aber ich verstehe

TypeError: zyklischer Objektwert

wegen der Konstrukte, die ich erwähnt habe.

Wie könnte ich das umgehen? Es ist mir egal, ob diese Verweise auf andere Knoten im serialisierten Objekt dargestellt werden oder nicht.

Andererseits scheint es mühsam, diese Eigenschaften beim Erstellen aus dem Objekt zu entfernen, und ich möchte keine Änderungen am Parser (Narzisse) vornehmen.


1
Ohne Code können wir Ihnen nicht helfen. Bitte veröffentlichen Sie die relevanten Bits Ihres Objekts und / oder der JSON-Ausgabe zusammen mit dem JS, mit dem Sie es serialisieren.
Bojangles

1
Können Sie den Eigenschaften, die interne Referenzen sind, ein Präfix hinzufügen?
Wheresrhys

@Loic Es wäre wertvoll, Douglas Crockfords cycle.jsals Antwort hier zu haben, da dies für viele Fälle die am besten geeignete Lösung ist. Es erscheint angemessen, diese Antwort zu veröffentlichen, da Sie der erste sind, der darauf verweist (in Ihrem Kommentar unten). Wenn Sie keine Lust haben, es selbst als Antwort zu veröffentlichen, werde ich es irgendwann tun.
Jeremy Banks


1
Ich wünschte, JSON wäre schlauer oder eine einfachere Möglichkeit, dies zu lösen. Die Lösungen sind zu einfach für einfache (!) Debugging-Zwecke imo.
BluE

Antworten:


220

Verwenden Sie den zweiten Parameter von stringify, die Ersetzungsfunktion , um bereits serialisierte Objekte auszuschließen:

var seen = [];

JSON.stringify(obj, function(key, val) {
   if (val != null && typeof val == "object") {
        if (seen.indexOf(val) >= 0) {
            return;
        }
        seen.push(val);
    }
    return val;
});

http://jsfiddle.net/mH6cJ/38/

Wie in anderen Kommentaren richtig ausgeführt, entfernt dieser Code jedes "gesehene" Objekt, nicht nur "rekursive".

Zum Beispiel für:

a = {x:1};
obj = [a, a];

Das Ergebnis ist falsch. Wenn Ihre Struktur so ist, möchten Sie möglicherweise Crockfords Decycle oder diese (einfachere) Funktion verwenden, die nur rekursive Referenzen durch Nullen ersetzt:

function decycle(obj, stack = []) {
    if (!obj || typeof obj !== 'object')
        return obj;
    
    if (stack.includes(obj))
        return null;

    let s = stack.concat([obj]);

    return Array.isArray(obj)
        ? obj.map(x => decycle(x, s))
        : Object.fromEntries(
            Object.entries(obj)
                .map(([k, v]) => [k, decycle(v, s)]));
}

//

let a = {b: [1, 2, 3]}
a.b.push(a);

console.log(JSON.stringify(decycle(a)))


3
aaah schön! Danke, ich werde es versuchen. Ich habe eine von Douglas Crockford erstellte Lösung gefunden ( github.com/douglascrockford/JSON-js/blob/master/cycle.js ), aber da ich mir nicht sicher bin, welche Lizenz dazu gehört, wäre die von Ihnen beschriebene einfache Lösung perfekt!
Loic Duros

3
@LoicDuros Die Lizenz ist "gemeinfrei". Das heißt, Sie können damit alles machen, was Sie wollen.
Ates Goral

1
Dieser Code erzeugt Fahrradschleifen, Vorsicht vor der Verwendung, sehr potenzielle Abstürze Ihrer App. benötigt korrekte Semikolons und kann nicht für Ereignisobjekte verwendet werden!
Ol Sen

3
Dies entfernt mehr als nur zyklische Referenzen - es entfernt einfach alles, was mehr als einmal erscheint. Sofern das bereits serialisierte Objekt kein "übergeordnetes" Objekt des neuen Objekts ist, sollten Sie es nicht löschen
Gio

1
Gute Antwort! Ich habe dies ein wenig geändert und die Funktion in eine rekursive Funktion geändert, sodass untergeordnete Objekte so geklont werden, wie übergeordnete Objekte geklont werden.
HoldOffHunger

2

Ich habe einen GitHub Gist erstellt, der zyklische Strukturen erkennen und auch de- und codieren kann: https://gist.github.com/Hoff97/9842228

Verwenden Sie zum Transformieren einfach JSONE.stringify / JSONE.parse. Es de- und codiert auch Funktionen. Wenn Sie dies deaktivieren möchten, entfernen Sie einfach die Zeilen 32-48 und 61-85.

var strg = JSONE.stringify(cyclicObject);
var cycObject = JSONE.parse(strg);

Ein Beispiel für eine Geige finden Sie hier:

http://jsfiddle.net/hoff97/7UYd4/


2

Dies ist eine Art alternative Antwort, aber da viele Leute hierher kommen werden, um ihre kreisförmigen Objekte zu debuggen, und es keine wirklich gute Möglichkeit gibt, dies zu tun, ohne eine Menge Code einzuziehen, geht es weiter.

Eine Funktion, die nicht so bekannt ist wie sie JSON.stringify()ist console.table(). Rufen console.table(whatever);Sie einfach auf , und die Variable wird in tabellarischer Form in der Konsole protokolliert, wodurch es ziemlich einfach und bequem ist, den Inhalt der Variablen zu lesen.


1

viel sparen und es zeigt, wo ein Zyklusobjekt war.

<script>
var jsonify=function(o){
    var seen=[];
    var jso=JSON.stringify(o, function(k,v){
        if (typeof v =='object') {
            if ( !seen.indexOf(v) ) { return '__cycle__'; }
            seen.push(v);
        } return v;
    });
    return jso;
};
var obj={
    g:{
        d:[2,5],
        j:2
    },
    e:10
};
obj.someloopshere = [
    obj.g,
    obj,
    { a: [ obj.e, obj ] }
];
console.log('jsonify=',jsonify(obj));
</script>

produziert

jsonify = {"g":{"d":[2,5],"j":2},"e":10,"someloopshere":[{"d":[2,5],"j":2},"__cycle__",{"a":[10,"__cycle__"]}]}

Aber es gibt immer noch ein Problem mit diesem Code, wenn jemand ein Objekt mit erstellen würde, obj.b=this'wenn jemand weiß, wie man sehr lange Berechnungen verhindert, die mit einem falsch vorgegebenen Bereich durchgeführt wurden. Es thiswäre schön, dies hier zu sehen
Ol Sen

2
Dies sollte seinseen.indexOf(v) != -1

1

Ich erstelle auch ein Github-Projekt, das zyklische Objekte serialisieren und die Klasse wiederherstellen kann, wenn Sie sie wie ein String im Attribut serializename speichern

var d={}
var a = {b:25,c:6,enfant:d};
d.papa=a;
var b = serializeObjet(a);
assert.equal(  b, "{0:{b:25,c:6,enfant:'tab[1]'},1:{papa:'tab[0]'}}" );
var retCaseDep = parseChaine(b)
assert.equal(  retCaseDep.b, 25 );
assert.equal(  retCaseDep.enfant.papa, retCaseDep );

https://github.com/bormat/serializeStringifyParseCyclicObject

Bearbeiten: Ich habe mein Skript für NPM https://github.com/bormat/borto_circular_serialize transformiert und die Funktionsnamen von Französisch auf Englisch geändert.


Dieses Beispiel passt nicht zum Kern. Der Kern hat Fehler.
Ernst Ernst

Gute Idee - aber machen Sie es einmal fertig :-) Wenn Sie es in npm verteilen würden, würden Sie vielleicht sogar Typisierungen dafür entwickeln, es wurde wahrscheinlich ziemlich beliebt.
Peter - Wiedereinstellung Monica

1

Hier ist ein Beispiel für eine Datenstruktur mit zyklischen Referenzen: toolshedCY

function makeToolshed(){
    var nut = {name: 'nut'}, bolt = {name: 'bolt'};
    nut.needs = bolt; bolt.needs = nut;
    return { nut: nut, bolt: bolt };
}

Wenn Sie die zyklischen Referenzen BEHALTEN möchten (stellen Sie sie beim Deserialisieren wieder her, anstatt sie zu "nuken"), haben Sie zwei Möglichkeiten, die ich hier vergleichen werde. Erstens ist Douglas Crockfords cycle.js , zweitens ist mein Sibirien- Paket. Beide arbeiten, indem sie zuerst das Objekt "decycling", dh ein anderes Objekt (ohne zyklische Referenzen) konstruieren, "das die gleichen Informationen enthält".

Herr Crockford geht zuerst:

JSON.decycle(makeToolshed())

JSON_decycleMakeToolshed

Wie Sie sehen, bleibt die verschachtelte Struktur von JSON erhalten, aber es gibt eine neue Sache, nämlich Objekte mit der speziellen $refEigenschaft. Mal sehen, wie das funktioniert.

root = makeToolshed();
[root.bolt === root.nut.needs, root.nut.needs.needs === root.nut]; // retutrns [true,true]

Das Dollarzeichen steht für die Wurzel. .boltNachdem $refwir erfahren haben, dass .boltes sich um ein "bereits gesehenes" Objekt handelt, und der Wert dieser speziellen Eigenschaft (hier der String $ ["nut"] ["need"]) sagt uns, wo, siehe zuerst ===oben. Ebenso für die zweite $refund die zweite ===oben.

Verwenden wir einen geeigneten Tiefengleichheitstest (nämlich Anders Kaseorgs deepGraphEqualFunktion aus der akzeptierten Antwort auf diese Frage ), um festzustellen, ob das Klonen funktioniert.

root = makeToolshed();
clone = JSON.retrocycle(JSON.decycle(root));
deepGraphEqual(root, clone) // true
serialized = JSON.stringify(JSON.decycle(root));
clone2 = JSON.retrocycle(JSON.parse(serialized));
deepGraphEqual(root, clone2); // true

Nun, Sibirien:

JSON.Siberia.forestify(makeToolshed())

JSON_Siberia_forestify_makeToolshed

Sibirien versucht nicht, "klassisches" JSON nachzuahmen, keine verschachtelte Struktur. Der Objektgraph wird "flach" beschrieben. Jeder Knoten des Objektgraphen wird in einen flachen Baum umgewandelt (einfache Schlüsselwertpaarliste mit nur ganzzahligen Werten). Dies ist ein Eintrag in .forest.Bei Index Null finden wir das Stammobjekt, bei höheren Indizes finden wir die anderen Knoten von Das Objektdiagramm und negative Werte (eines Schlüssels eines Baums der Gesamtstruktur) verweisen auf das atomsArray (das über das Array types eingegeben wird, aber die Eingabedetails werden hier übersprungen). Alle Endknoten befinden sich in der Atomtabelle, alle nicht-Endknoten befinden sich in der Gesamtstrukturtabelle, und Sie können sofort sehen, wie viele Knoten der Objektgraph hat, nämlich forest.length. Testen wir, ob es funktioniert:

root = makeToolshed();
clone = JSON.Siberia.unforestify(JSON.Siberia.forestify(root));
deepGraphEqual(root, clone); // true
serialized = JSON.Siberia.stringify(JSON.Siberia.forestify(root));
clone2 = JSON.Siberia.unforestify(JSON.Siberia.unstringify(serialized));
deepGraphEqual(root, clone2); // true

Vergleich

wird später einen Abschnitt hinzufügen.


0
function stringifyObject ( obj ) {
  if ( _.isArray( obj ) || !_.isObject( obj ) ) {
    return obj.toString()
  }
  var seen = [];
  return JSON.stringify(
    obj,
    function( key, val ) {
      if (val != null && typeof val == "object") {
        if ( seen.indexOf( val ) >= 0 )
          return
          seen.push( val )
          }
      return val
    }
  );
}

Eine Vorbedingung fehlte, andernfalls werden die ganzzahligen Werte in Array-Objekten abgeschnitten, dh [[08.11.2014 12:30:13, 1095]] 1095 wird auf 095 reduziert.


RefrenceError erhalten: Variable kann nicht gefunden werden: _
amit pandya

Bitte korrigieren Sie Ihren Code.
Anastasios Moraitis
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.