Come posso stampare una struttura circolare in un formato simile a JSON?


682

Ho un grande oggetto che voglio convertire in JSON e inviare. Tuttavia ha una struttura circolare. Voglio lanciare qualsiasi riferimento circolare e inviare tutto ciò che può essere stretto. Come lo faccio?

Grazie.

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

Voglio stringere obj in:

{"a":"foo"}

5
Potresti pubblicare un oggetto campione con un riferimento circolare che desideri analizzare?
TWickz,

3
qualcosa di simile a questo ?
Alvin Wong,


2
In ritardo alla festa, ma c'è un progetto github per gestirlo.
Preston S,

questione strettamente correlato: stackoverflow.com/questions/23117470/...
mathheadinclouds

Risposte:


607

Utilizzare JSON.stringifycon un sostituto personalizzato. Per esempio:

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

Il sostituto in questo esempio non è corretto al 100% (a seconda della definizione di "duplicato"). Nel seguente caso, un valore viene scartato:

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

Ma il concetto è valido: utilizzare un sostituto personalizzato e tenere traccia dei valori degli oggetti analizzati.

Come funzione di utilità scritta in es6:

// 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 Qual è il bug? Sarò lieto di risolvere la risposta, se ci sono inesattezze in essa.
Rob W,

1
@CruzDiablo La serializzazione del DOM di solito non ha senso. Tuttavia, se riesci a pensare a un metodo di serializzazione significativo per i tuoi scopi, puoi provare ad aggiungere una serializzazione personalizzata agli oggetti DOM: Node.prototype.toJSON = function() { return 'whatever you think that is right'; };(se vuoi qualcosa di più generico / specifico, prova qualsiasi cosa nell'albero del prototipo: HTMLDivElement implementa gli strumenti HTMLElement Element implementa Node implementa EventTarget; nota: questo può dipendere dal browser, l'albero precedente è vero per Chrome)
Rob W

7
questo è sbagliato perché salterà la seconda apparizione di oggetti che sono contenuti due volte, anche se non in una struttura veramente ciclica. var a={id:1}; JSON.stringify([a,a]);
user2451227

3
@ user2451227 "Il sostituto in questo esempio non è corretto al 100% (a seconda della definizione di" duplicato "). Ma il concetto è valido: utilizzare un sostituto personalizzato e tenere traccia dei valori degli oggetti analizzati."
Rob W

4
La preoccupazione GC qui è probabilmente ridondante. Se questo viene eseguito come singolo script, lo script termina immediatamente. Se questo è incapsulato all'interno di una funzione per l'implementazione, allora cachesarà irraggiungibile developer.mozilla.org/en-US/docs/Web/JavaScript/…
Trindaz

704

In Node.js è possibile utilizzare util.inspect (oggetto) . Sostituisce automaticamente i collegamenti circolari con "[Circolare]".


Sebbene sia incorporato (non è richiesta l'installazione) , è necessario importarlo

import * as util from 'util' // has no default export
import { inspect } from 'util' // or directly
// or 
var util = require('util')
Per usarlo, basta chiamare
console.log(util.inspect(myObject))

Inoltre, tieni presente che puoi passare l'oggetto delle opzioni da ispezionare (vedi link sopra)

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



Per favore, leggi e dai i miei complimenti ai commentatori qui sotto ...


134
util è un modulo integrato, non è necessario installarlo.
Mitar,

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

19
@Mitar è integrato, ma devi ancora caricare il modulovar util = require('util');
bodecker

14
Non essere un somaro come me, è solo obj_str = util.inspect(thing) , NON <s> garbage_str = JSON.stringify(util.inspect(thing))</s>
ThorSummoner,

7
Questo è molto meglio che scherzare con il controllo dei tipi. Perché non si può semplicemente stringere il lavoro in questo modo? Se sa che c'è un riferimento circolare, perché non si può semplicemente dire di ignorarlo ???
Chris Peacock,

141

Mi chiedo perché nessuno abbia ancora pubblicato la soluzione corretta dalla pagina MDN ...

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

I valori visti devono essere memorizzati in un set , non in un array (il sostituto viene chiamato su ogni elemento ) e non è necessario provare JSON.stringify ogni elemento nella catena che porta a un riferimento circolare.

Come nella risposta accettata, questa soluzione rimuove tutti i valori ripetuti , non solo quelli circolari. Ma almeno non ha complessità esponenziale.


Pulito, ma questo è solo ES2015. Nessun supporto IE.
Martin Capodici,

44
Yoda dice: "Se lo supporta ancora IE, allora dovresti usare un transpiler."
Treno Spagna

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)ritorna undefinedin cromo
roberto tomás,

1
Funziona in React + Typescript. grazie
user3417479

76

basta fare

npm i --save circular-json

quindi nel tuo file js

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

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

NOTA: non ho nulla a che fare con questo pacchetto. Ma lo uso per questo.

Aggiornamento 2020

Si prega di notare che CircularJSON è solo in manutenzione e il suo successore è piatto .


Molte grazie! Grande biblioteca, risparmiato tonnellate di tempo. Super piccolo (solo 1,4 KB minimizzato).
Brian Haak,

16
Penso che potresti aver bisogno di qualche giustificazione in più per l'utilizzo di un modulo rispetto a "basta fare". E non è bello sovrascrivere JSONper principio.
Edwin,

Avevo bisogno di copiare un oggetto da usare per i test sugli stub. Questa risposta è stata perfetta. Ho copiato l'oggetto e quindi rimosso l'override. Grazie!!
Chris Sharp,

1
Secondo l'autore, questo pacchetto è stato deprecato. CircularJSON è solo in manutenzione, il suo successore è appiattito. Link: github.com/WebReflection/flatted#flatted
Robert Molina

3
Attenzione, il pacchetto 'flatted' (e circular-json?) Non replica la funzionalità JSON.stringify (). Crea il proprio formato non JSON. (ad es. Flatted.stringify({blah: 1})risultati [{"blah":1}]) Vedo che qualcuno ha provato a sollevare un problema al riguardo, e l'autore li ha rimproverati e ha bloccato il problema ai commenti.
jameslol,

48

Mi è piaciuta molto la soluzione di Trindaz - più dettagliata, tuttavia aveva alcuni bug. Li ho riparati anche a chi piace.

Inoltre, ho aggiunto un limite di lunghezza ai miei oggetti cache.

Se l'oggetto che sto stampando è davvero grande - intendo infinitamente grande - voglio limitare il mio algoritmo.

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

Manca un controllo null su questa riga: return "(vedi" + (!! value.constructor? Value.constructor.name.toLowerCase (): typeof (value)) + "with key" + stampatoObjectKeys [PrintedObjIndex] + ")";
Isak,

Lo aggiungerò volentieri. fammi sapere cosa è nulla poiché ho riscontrato problemi finora.
guy mograbi,

2
// i browser non stamperanno più di 20K - Ma hai impostato un limite di 2k. Forse cambi per il futuro?
Pochen,

38

La risposta di @ RobW è corretta, ma è più performante! Perché utilizza un hashmap / set:

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

Per oggetti profondamente annidati con riferimenti circolari, prova stringifyDeep => github.com/ORESoftware/safe-stringify
Alexander Mills

È possibile che l'implementazione di Set utilizzi solo un array e un indexOf sotto il cofano, ma non l'ho confermato.
Alexander Mills,

Ciò sta rimuovendo i nodi padre che hanno nodi figlio anche con valori diversi - ad es. - {"a":{"b":{"a":"d"}}}e persino rimuovendo nodi con oggetto vuoto {}
Sandip Pingle

Puoi mostrare un esempio di quel Sandip? creare un gist.github.com o quant'altro
Alexander Mills

Eccellente !!! Prima (dall'alto, ma solo 2-3 soluzioni funzionanti) soluzione funzionante qui sotto node.js e Fission ;-) - librerie appese.
Tom,

37

Si noti che esiste anche un JSON.decyclemetodo implementato da Douglas Crockford. Vedi il suo cycle.js . Ciò consente di stringere quasi qualsiasi struttura standard:

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

È inoltre possibile ricreare l'oggetto originale con il retrocyclemetodo. Quindi non è necessario rimuovere i cicli dagli oggetti per renderli più stringenti.

Tuttavia, ciò non funzionerà per i nodi DOM (che sono la causa tipica dei cicli nei casi d'uso reali). Ad esempio questo genererà:

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

Ho fatto un fork per risolvere quel problema (vedi il mio fork.js fork ). Questo dovrebbe funzionare bene:

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

Nota che nel mio fork JSON.decycle(variable)funziona come nell'originale e genererà un'eccezione quando variablecontiene nodi / elementi DOM.

Quando lo usi JSON.decycle(variable, true), accetti il ​​fatto che il risultato non sarà reversibile (retrocycle non ricrea i nodi DOM). Tuttavia, gli elementi DOM dovrebbero essere identificabili in una certa misura. Ad esempio, se un divelemento ha un id, verrà sostituito con una stringa "div#id-of-the-element".


2
Sia il suo codice che il tuo mi danno un "RangeError: dimensione massima dello stack di chiamate superata" quando li uso.
jcollum,

Posso dare un'occhiata se fornisci il tuo codice sul violino o aggiungi un problema su Github: github.com/Eccenux/JSON-js/issues
Nux

Questo è quello che stavo cercando. JSON.decycle(a, true)cosa succede quando si passa a true come parametro per decodificare la funzione.
Rudra,

@Rudra true rende vera l' stringifyNodesopzione nel fork. Questo sarà il dump ad esempio divcon id = "qualche-id" a stringa: div#some-id. Eviterai alcuni problemi, ma non sarai in grado di retrocedere completamente.
Nux,

C'è un pacchetto npm npmjs.com/package/json-js , ma non è stato aggiornato per un po '
Michael Freidgeim,

23

Consiglio di dare un'occhiata a json-stringify-safe da @ isaacs-- è usato in NPM.

A proposito: se non stai usando Node.js, puoi semplicemente copiare e incollare le righe 4-27 dalla parte pertinente del codice sorgente .

Installare:

$ npm install json-stringify-safe --save

Usare:

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

Questo produce:

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

Si noti che, proprio come con la funzione vaniglia JSON.stringify come menzionato da @Rob W, è anche possibile personalizzare il comportamento di sanificazione passando una funzione "replacer" come secondo argomento stringify(). Se ti trovi ad avere bisogno di un semplice esempio di come farlo, ho appena scritto un sostituto personalizzato che costringe errori, regexps e funzioni in stringhe leggibili dall'uomo qui .


13

Per i futuri googler alla ricerca di una soluzione a questo problema quando non si conoscono i tasti di tutti i riferimenti circolari, è possibile utilizzare un wrapper attorno alla funzione JSON.stringify per escludere i riferimenti circolari. Vedi uno script di esempio su https://gist.github.com/4653128 .

La soluzione si riduce essenzialmente a mantenere un riferimento a oggetti precedentemente stampati in un array e verificarlo in una funzione di sostituzione prima di restituire un valore. È più restrittivo del solo escludere riferimenti circolari, perché esclude anche di stampare due volte un oggetto, uno dei cui effetti collaterali è quello di evitare riferimenti circolari.

Esempio di 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
Bel codice. Tuttavia, hai un errore sciocco, scrivi if(printedObjIndex)mentre dovresti scrivere if(printedObjIndex==false)perché indexpuò anche essere 0tradotto in falsese non diversamente specificato.
guy mograbi,

1
@guymograbi Non intendi ===? 0 == falseè true, 0 === falseè false. ; ^) Ma preferirei non inizializzare printedObjIndexsu falso, dato che poi puoi verificare contro in undefinedmodo che tu (beh, di Trindaz) non mescoli le metafore in modo strano.
ruffin,

@Ruffin bella cattura. sì, ovviamente, usa sempre una forte uguaglianza e jshint per cogliere errori così sciocchi.
guy mograbi,

4

Utilizzare il metodo JSON.stringify con un sostituto. Leggi questa documentazione per ulteriori informazioni. 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));

Scopri un modo per popolare l'array di sostituzione con riferimenti ciclici. È possibile utilizzare il metodo typeof per scoprire se una proprietà è di tipo 'oggetto' (riferimento) e un controllo esatto dell'uguaglianza (===) per verificare il riferimento circolare.


4
Questo potrebbe funzionare solo in IE (considerando il fatto che MSDN è una documentazione di Microsoft e Microsoft crea IE). In Firefox / Chrome, jsfiddle.net/ppmaW genera l'errore di riferimento circolare. A proposito: var obj = {foo:obj}non non creare un riferimento circolare. Invece, crea un oggetto il cui fooattributo si riferisce al valore precedente di obj( undefinedse non precedentemente definito, dichiarato a causa di var obj).
Rob W,

4

Se

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

risulta in a

TypeError: valore oggetto ciclico

Quindi potresti voler stampare in questo modo:

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

21
Forse perché stampa solo un livello?
Alex Turpin,

MOLTO SEMPLICE l'ho votato perché ha funzionato per me fin da subito in cromo. ECCELLENTE
Amore e pace - Joe Codeswell,

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

valuta di:

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

con la funzione:

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

So che questa è una vecchia domanda, ma vorrei suggerire un pacchetto NPM che ho creato chiamato smart-circular , che funziona diversamente dagli altri modi proposti. È particolarmente utile se stai usando oggetti grandi e profondi .

Alcune caratteristiche sono:

  • Sostituzione di riferimenti circolari o semplicemente ripetute strutture all'interno dell'oggetto con il percorso che porta alla sua prima occorrenza (non solo la stringa [circolare] );

  • Cercando le circolarità in una ricerca di ampiezza, il pacchetto assicura che questo percorso sia il più piccolo possibile, il che è importante quando si tratta di oggetti molto grandi e profondi, in cui i percorsi possono diventare fastidiosamente lunghi e difficili da seguire (la sostituzione personalizzata in JSON.stringify esegue un DFS);

  • Permette sostituzioni personalizzate, utili per semplificare o ignorare parti meno importanti dell'oggetto;

  • Infine, i percorsi sono scritti esattamente nel modo necessario per accedere al campo di riferimento, che può aiutarti a eseguire il debug.


3

Il secondo argomento di JSON.stringify () consente anche di specificare un array di nomi chiave che dovrebbe essere preservato da ogni oggetto che incontra all'interno dei dati. Questo potrebbe non funzionare per tutti i casi d'uso, ma è una soluzione molto più semplice.

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

Nota: Stranamente, la definizione dell'oggetto da OP non genera un errore di riferimento circolare nell'ultimo Chrome o Firefox. La definizione di cui questa risposta è stato modificato in modo che esso ha generato un errore.



Questa dovrebbe essere una risposta accettata
Manic Depression il

2

Per aggiornare la risposta della sostituzione del modo in cui funziona JSON (probabilmente non consigliato, ma super semplice), non utilizzare circular-json(è obsoleto). Invece, usa il successore, appiattito:

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

Preso in prestito dalla precedente risposta di @ user1541685, ma sostituito con quello nuovo:

npm i --save flatted

quindi nel tuo file js

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

1

Ho trovato la libreria circolare json su github e ha funzionato bene per il mio problema.

Alcune buone funzionalità che ho trovato utili:

  • Supporta l'utilizzo multipiattaforma, ma finora l'ho testato solo con node.js.
  • L'API è la stessa, quindi tutto ciò che devi fare è includere e usarlo come sostituto di JSON.
  • Ha il proprio metodo di analisi in modo da poter riconvertire i dati serializzati 'circolari' in oggetto.

2
Questa libreria ha generato un errore per me, quindi devo cercarne un altro. ERRORE TypeError: toISOString non è una funzione in String.toJSON (<anonymous>) in Object. <anonymous> ( localhost: 8100 / build / polyfills.js: 1: 3458 ) in JSON.stringify (<anonymous>) in Object. stringifyRecursion [come stringify] ( localhost: 8100 / build / main.js: 258450: 15 )
Mark Ellul,

1
@MarkEllul Ho scritto il commento nel 2015 e se vedrò un'alternativa migliore lo posterò qui con una modifica. Di tanto in tanto finisco lo stesso problema nel lavoro quotidiano e di solito preferisco le mie funzioni manuali in modo ricorsivo con un'ispezione adeguata / sicura. Suggerirei di provare pratiche di programmazione funzionale se non hai familiarità, di solito, sta facilitando questo tipo di operazioni ricorsive in quanto meno complicate e più affidabili.
JacopKane,

Anche ottenere "toISOString non è una funzione" cercando di stringere un evento e inviarlo di nuovo in un test di cipresso
Devin G Rhode

1

Risolvo questo problema in questo modo:

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

Questo ha funzionato praticamente per me, ma sembra che le classi siano state rappresentate come _class: ClassName { data: "here" }, quindi ho aggiunto la seguente regola .replace(/(\w+) {/g, '{ __ClassName__: "$1", '). Nel mio caso stavo cercando di vedere che aspetto aveva un oggetto di richiesta http.
redbmk,

1

So che questa domanda è vecchia e ha molte ottime risposte, ma inserisco questa risposta a causa del suo nuovo sapore (es5 +)


1

Sebbene sia stata data una risposta sufficiente, è anche possibile eliminare esplicitamente la proprietà in questione prima della stringa utilizzando l' deleteoperatore.

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

cancella operatore

questo eliminerà la necessità di costruire o mantenere una logica complessa per rimuovere riferimenti circolari.


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

Sulla base delle altre risposte finisco con il seguente codice. Funziona abbastanza bene con riferimenti circolari, oggetti con costruttori personalizzati.

Dall'oggetto dato da serializzare,

  • Memorizza nella cache tutti gli oggetti che incontri mentre attraversi l'oggetto e assegna a ciascuno di essi un hashID univoco (funziona anche un numero a incremento automatico)
  • Una volta trovato un riferimento circolare, contrassegnare quel campo nel nuovo oggetto come circolare e memorizzare l'hashID dell'oggetto originale come attributo.

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;

Esempio di utilizzo 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));

Esempio di utilizzo 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

Prova questo:

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

Non dovrebbero esserci, come, poche altre righe di codice dopo il seen.push(value)= -D? Mi piacefor (var key in value) {value[key] = circular_replacer(value[key]);}
Klesun l'

Le risposte di solo codice sono scoraggiate. Fai clic su Modifica e aggiungi alcune parole che riassumono in che modo il tuo codice risponde alla domanda, o forse spiega in che modo la tua risposta differisce dalla precedente / risposte. Dalla recensione
Nick

0

Nella mia soluzione, se ti imbatti in un ciclo, non dice semplicemente "ciclo" (o niente), dice qualcosa come foo: vedi l'oggetto # 42 sopra e per vedere dove punti foo puntano su puoi scorrere verso l'alto e cercare per l'oggetto # 42 (ogni oggetto, quando si avvia, dice l'oggetto # xxx con un numero intero xxx)

Frammento:

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

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.