Serializzazione di un oggetto che contiene un valore oggetto ciclico


151

Ho un oggetto (albero di analisi) che contiene nodi figlio che sono riferimenti ad altri nodi.

Vorrei serializzare questo oggetto, usando JSON.stringify(), ma ottengo

TypeError: valore oggetto ciclico

a causa dei costrutti che ho citato.

Come potrei aggirare questo? Non mi importa se questi riferimenti ad altri nodi sono rappresentati o meno nell'oggetto serializzato.

D'altra parte, rimuovere queste proprietà dall'oggetto quando vengono create sembra noioso e non vorrei apportare modifiche al parser (narciso).


1
Non possiamo aiutarti senza un po 'di codice. Si prega di pubblicare i bit rilevanti dell'oggetto e / o dell'output JSON insieme al JS utilizzato per serializzarlo.
Bojangles,

1
sei in grado di aggiungere un prefisso a quelle proprietà che sono riferimenti interni?
whereesrhys

@Loic Sarebbe utile avere cycle.jsqui la risposta di Douglas Crockford , poiché è la soluzione più appropriata per molti casi. Ti sembra appropriato pubblicare quella risposta, dal momento che sei il primo a fare riferimento a essa (nel tuo commento qui sotto). Se non hai voglia di pubblicarlo come risposta, alla fine lo farò.
Jeremy Banks,


1
Vorrei che JSON fosse più intelligente, o un modo più semplice di risolverlo. Le soluzioni sono troppo problematiche per semplici (!) Scopi di debug imo.
BluE,

Risposte:


220

Utilizzare il secondo parametro di stringify, la funzione di sostituzione , per escludere oggetti già serializzati:

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/

Come sottolineato correttamente in altri commenti, questo codice rimuove tutti gli oggetti "visti", non solo quelli "ricorsivi".

Ad esempio, per:

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

il risultato sarà errato. Se la tua struttura è così, potresti voler usare il decycle di Crockford o questa funzione (più semplice) che sostituisce semplicemente i riferimenti ricorsivi con valori null:

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 bello! Grazie, ci proverò. Ho trovato una soluzione creata da Douglas Crockford ( github.com/douglascrockford/JSON-js/blob/master/cycle.js ), ma poiché non sono sicuro della licenza che ne consegue, la soluzione semplice che descrivi sarebbe perfetta!
Loic Duros,

3
@LoicDuros La licenza è "dominio pubblico". Cioè, puoi fare tutto quello che vuoi con esso.
Ates Goral,

1
questo codice produce cicli di ciclismo, attenzione all'utilizzo, potenziali arresti anomali della tua app. necessita di punti e virgola corretti e non è utilizzabile su oggetti evento!
Ol Sen,

3
Ciò rimuove più di semplici riferimenti ciclici: rimuove semplicemente tutto ciò che appare più di una volta. A meno che l'oggetto che è già stato serializzato non sia un "genitore" del nuovo oggetto, non dovresti eliminarlo
Gio

1
Buona risposta! L'ho modificato un po ', ho cambiato la funzione in una funzione ricorsiva, in modo che gli oggetti figlio vengano clonati nel modo in cui vengono clonati gli oggetti padre.
HoldOffHunger,

2

Ho creato un GitHub Gist in grado di rilevare strutture cicliche e anche di decodificarle e codificarle: https://gist.github.com/Hoff97/9842228

Per trasformare basta usare JSONE.stringify / JSONE.parse. Inoltre de- e codifica le funzioni. Se vuoi disabilitarlo, rimuovi le righe 32-48 e 61-85.

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

Puoi trovare un violino di esempio qui:

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


2

Questa è una specie di risposta alternativa, ma dal momento che molte persone verranno qui per il debug dei loro oggetti circolari e non c'è davvero un ottimo modo per farlo senza inserire un sacco di codice, ecco qui.

Una caratteristica che non è così conosciuta come JSON.stringify()è console.table(). Basta chiamare console.table(whatever);e registrerà la variabile nella console in formato tabulare, rendendo piuttosto semplice e conveniente esaminare il contenuto della variabile.


1

molto risparmiatore e mostra dove si trovava un oggetto ciclo .

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

produce

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

ma c'è ancora un problema con questo codice se qualcuno costruisse un oggetto obj.b=this'se qualcuno sapesse come prevenire calcoli molto lunghi fatti con un ambito sbagliato thissarebbe bello da vedere qui
Ol Sen

2
Dovrebbe essereseen.indexOf(v) != -1

1

Creo anche un progetto github in grado di serializzare l'oggetto ciclico e ripristinare la classe se lo salvi nell'attributo serializename come una stringa

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

Modifica: ho trasformato il mio script per NPM https://github.com/bormat/borto_circular_serialize e ho cambiato i nomi delle funzioni dal francese all'inglese.


Questo esempio non si adatta al Gist. The Gist ha errori.
Ernst Ernst,

Bella idea - ma una volta pronto :-) Se lo facessi distribuire in npm, forse svilupperesti anche le battiture per quello, probabilmente diventerà abbastanza popolare.
Peter - Ripristina Monica il

1

Ecco un esempio di una struttura di dati con riferimenti ciclici: toolshedCY

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

Quando desideri MANTENERE i riferimenti ciclici (ripristinali quando li deserializzi, invece di "sfumarli"), hai 2 opzioni, che confronterò qui. Il primo è il ciclo.js di Douglas Crockford , il secondo è il mio pacchetto Siberia . Entrambi funzionano innanzitutto "deciclando" l'oggetto, ovvero costruendo un altro oggetto (senza riferimenti ciclici) "contenente le stesse informazioni".

Il signor Crockford va per primo:

JSON.decycle(makeToolshed())

JSON_decycleMakeToolshed

Come vedi, la struttura nidificata di JSON viene mantenuta, ma c'è una nuova cosa, ovvero gli oggetti con la $refproprietà speciale . Vediamo come funziona.

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

Il simbolo del dollaro rappresenta la radice. .boltaverci $refdetto che .boltè un oggetto "già visto", e il valore di quella proprietà speciale (qui, la stringa $ ["nut"] ["needs"]) ci dice dove, vedi prima ===sopra. Allo stesso modo per il secondo $refe il secondo ===sopra.

Usiamo un test di uguaglianza profonda adeguato (vale a dire la deepGraphEqualfunzione di Anders Kaseorg dalla risposta accettata a questa domanda ) per vedere se la clonazione funziona.

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

Ora, Siberia:

JSON.Siberia.forestify(makeToolshed())

JSON_Siberia_forestify_makeToolshed

La Siberia non tenta di imitare JSON "classico", nessuna struttura nidificata. Il grafico dell'oggetto è descritto in modo "piatto". Ogni nodo del grafico degli oggetti viene trasformato in un albero piatto (elenco di coppie di valori di chiave semplice con valori solo interi), che è una voce in .forest.A indice zero, troviamo l'oggetto radice, a indici più alti, troviamo gli altri nodi di il grafico dell'oggetto e i valori negativi (di una chiave di un albero della foresta) puntano alla atomsmatrice (che viene digitata dalla matrice dei tipi, ma salteremo qui i dettagli di battitura). Tutti i nodi terminali sono nella tabella degli atomi, tutti i nodi non terminali sono nella tabella della foresta e puoi vedere subito quanti nodi ha il grafico a oggetti, vale a dire forest.length. Testiamo se funziona:

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

confronto

aggiungerà la sezione in seguito.


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

Mancava una condizione preliminare, altrimenti i valori interi negli oggetti array vengono troncati, ovvero [[08.11.2014 12:30:13, 1095]] 1095 viene ridotto a 095.


ottenere RefrenceError: impossibile trovare la variabile: _
pandya il

Si prega di correggere il codice.
Anastasios Moraitis il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.