Comment imprimer une structure circulaire dans un format de type JSON?

682

J'ai un gros objet que je veux convertir en JSON et envoyer. Cependant, il a une structure circulaire. Je veux lancer toutes les références circulaires existantes et envoyer tout ce qui peut être stratifié. Comment je fais ça?

Merci.

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

Je veux stringifier obj en:

{"a":"foo"}
Harry
la source
5
Pourriez-vous s'il vous plaît publier un exemple d'objet avec une référence circulaire que vous souhaitez analyser?
TWickz
3
quelque chose comme ça ?
Alvin Wong
1
doublon possible d'un objet
Oleg V. Volkov
2
Tard dans la fête mais il y a un projet github pour gérer ça.
Preston S
question étroitement liée: stackoverflow.com/questions/23117470/…
mathheadinclouds

Réponses:

607

À utiliser JSON.stringifyavec un remplaçant personnalisé. Par exemple:

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

Le remplaçant dans cet exemple n'est pas correct à 100% (selon votre définition de "doublon"). Dans le cas suivant, une valeur est ignorée:

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

Mais le concept est le suivant: utilisez un remplaçant personnalisé et gardez une trace des valeurs des objets analysés.

En tant que fonction utilitaire écrite en 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))
Rob W
la source
1
@Harry Quel est le bug? Je corrigerai volontiers la réponse, s'il y a des inexactitudes dedans.
Rob W
1
@CruzDiablo La sérialisation du DOM n'a généralement aucun sens. Cependant, si vous pouvez penser à une méthode de sérialisation significative pour vos besoins, vous pouvez essayer d'ajouter une sérialisation personnalisée aux objets DOM: Node.prototype.toJSON = function() { return 'whatever you think that is right'; };(si vous voulez quelque chose de plus générique / spécifique, essayez simplement n'importe quoi dans l'arborescence du prototype: HTMLDivElement implémente HTMLElement implémente Element implémente Node implémente EventTarget; remarque: cela peut dépendre du navigateur, l'arborescence précédente est vraie pour Chrome)
Rob W
7
c'est faux car cela sautera la deuxième apparition d'objets qui sont contenus deux fois, même s'ils ne sont pas dans une structure vraiment cyclique. var a={id:1}; JSON.stringify([a,a]);
user2451227
3
@ user2451227 "Le remplaçant dans cet exemple n'est pas correct à 100% (selon votre définition de" doublon "). Mais le concept est le suivant: utilisez un remplaçant personnalisé et gardez une trace des valeurs des objets analysés."
Rob W
4
La préoccupation du GC ici est sans doute redondante. Si cela est exécuté comme un seul script, le script se termine immédiatement. Si cela est encapsulé dans une fonction pour l'implémentation, alors cacheil sera inaccessible developer.mozilla.org/en-US/docs/Web/JavaScript/…
Trindaz
704

Dans Node.js, vous pouvez utiliser util.inspect (object) . Il remplace automatiquement les liens circulaires par "[Circulaire]".


Bien qu'il soit intégré (aucune installation n'est requise) , vous devez l'importer

import * as util from 'util' // has no default export
import { inspect } from 'util' // or directly
// or 
var util = require('util')
Pour l'utiliser, appelez simplement
console.log(util.inspect(myObject))

Sachez également que vous pouvez passer un objet d'options à inspecter (voir le lien ci-dessus)

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



S'il vous plaît, lisez et félicitez les commentateurs ci-dessous ...

Erel Segal-Halevi
la source
134
util est un module intégré, vous n'avez pas besoin de l'installer.
Mitar
10
console.log (util.inspect (obj))
starsinmypockets
19
@Mitar il est intégré, mais vous devez toujours charger le modulevar util = require('util');
bodecker
14
Ne soyez pas un cancre comme moi, c'est juste obj_str = util.inspect(thing) , PAS <s> garbage_str = JSON.stringify(util.inspect(thing))</s>
ThorSummoner
7
C'est beaucoup mieux que de contourner les types de vérification. Pourquoi ne peut-on pas simplement stringifier comme ça? S'il sait qu'il y a une référence circulaire, pourquoi ne peut-on pas simplement lui dire de l'ignorer ???
Chris Peacock
141

Je me demande pourquoi personne n'a encore posté la bonne solution à partir de la page 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());

Les valeurs vues doivent être stockées dans un ensemble , pas dans un tableau (le remplaçant est appelé sur chaque élément ) et il n'est pas nécessaire d'essayer JSON.stringify chaque élément de la chaîne menant à une référence circulaire.

Comme dans la réponse acceptée, cette solution supprime toutes les valeurs répétitives , pas seulement les valeurs circulaires. Mais au moins, il n'a pas de complexité exponentielle.

Klesun
la source
C'est bien, mais c'est ES2015 uniquement. Pas de support IE.
Martin Capodici
44
Yoda dit: "Si le support d'IE l'est toujours, alors utilisez un transpilateur."
Espagne Train
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)revient undefineden chrome
roberto tomás
1
Cela fonctionne dans React + Typescript. merci
user3417479
76

fais juste

npm i --save circular-json

puis dans votre fichier js

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

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

REMARQUE: je n'ai rien à voir avec ce package. Mais je l'utilise pour cela.

Mise à jour 2020

Veuillez noter que CircularJSON est en maintenance uniquement et aplati est son successeur.

user1541685
la source
Merci beaucoup! Grande bibliothèque, économisé des tonnes de temps. Super minuscule (seulement 1,4 Ko minifié).
Brian Haak
16
Je pense que vous pourriez avoir besoin de plus de justification pour utiliser un module que "juste faire". Et ce n'est pas génial d'écraser JSONpar principe.
Edwin
J'avais besoin de copier un objet à utiliser pour les tests de stub. Cette réponse était parfaite. J'ai copié l'objet, puis supprimé le remplacement. Merci!!
Chris Sharp
1
Selon l'auteur, ce package est obsolète. CircularJSON est en maintenance uniquement, aplati est son successeur. Lien: github.com/WebReflection/flatted#flatted
Robert Molina
3
Attention, le package 'flat' (et circulaire-json?) Ne réplique pas la fonctionnalité JSON.stringify (). Il crée son propre format non JSON. (par exemple, Flatted.stringify({blah: 1})résultats en [{"blah":1}]) Je vois que quelqu'un a essayé de soulever un problème à ce sujet, et l'auteur les a réprimandés et a verrouillé le problème aux commentaires.
jameslol
48

J'ai vraiment aimé la solution de Trindaz - plus verbeuse, mais elle avait quelques bugs. Je les ai réparés pour ceux qui aiment ça aussi.

De plus, j'ai ajouté une limite de longueur à mes objets de cache.

Si l'objet que j'imprime est vraiment grand - je veux dire infiniment grand - je veux limiter mon algorithme.

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);
};
guy mograbi
la source
Vous manquez une vérification nulle sur cette ligne: return "(voir" + (!! value.constructor? Value.constructor.name.toLowerCase (): typeof (value)) + "with key" + PrintObjectKeys [PrintObjIndex] + ")";
Isak
Je l'ajouterai volontiers. faites-moi simplement savoir ce qui est annulable car j'ai rencontré des problèmes jusqu'à présent.
guy mograbi
2
// les navigateurs n'imprimeront pas plus de 20K - Mais vous mettez la limite à 2k. Peut-être changer pour l'avenir?
Pochen
38

@ La réponse de RobW est correcte, mais c'est plus performant! Parce qu'il utilise 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;
  });
};
Alexander Mills
la source
Pour les objets profondément imbriqués avec des références circulaires, essayez stringifyDeep => github.com/ORESoftware/safe-stringify
Alexander Mills
Il est possible que l'implémentation Set utilise simplement un tableau et un indexOf sous le capot, mais je ne l'ai pas confirmé.
Alexander Mills
Cela supprime les nœuds parents ayant des nœuds enfants même avec des valeurs différentes - par exemple - {"a":{"b":{"a":"d"}}}et même la suppression des nœuds ayant un objet vide {}
Sandip Pingle
Pouvez-vous montrer un exemple de ce Sandip? créer un gist.github.com ou autre
Alexander Mills
Excellent !!! Tout d'abord (à partir du haut, mais vérifié seulement 2-3 solutions de fonction) solution de travail ici sous node.js et Fission ;-) - bibliothèques suspendues.
Tom
37

Notez qu'il existe également une JSON.decycleméthode implémentée par Douglas Crockford. Voir son cycle.js . Cela vous permet de stringifier presque n'importe quelle structure standard:

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

Vous pouvez également recréer un objet d'origine avec la retrocycleméthode. Vous n'avez donc pas à supprimer les cycles des objets pour les stringifier.

Cependant, cela ne fonctionnera pas pour les nœuds DOM (qui sont la cause typique des cycles dans des cas d'utilisation réels). Par exemple, cela lancera:

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

J'ai fait une fourchette pour résoudre ce problème (voir ma fourche cycle.js ). Cela devrait fonctionner correctement:

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

Notez que dans ma fourchette JSON.decycle(variable)fonctionne comme dans l'original et lèvera une exception lorsque les variablenœuds / éléments DOM contiennent.

Lorsque vous utilisez, JSON.decycle(variable, true)vous acceptez le fait que le résultat ne sera pas réversible (le rétrocycle ne recréera pas les nœuds DOM). Les éléments DOM doivent cependant être identifiables dans une certaine mesure. Par exemple, si un divélément a un identifiant, il sera remplacé par une chaîne "div#id-of-the-element".

Nux
la source
2
Son code et le vôtre me donnent une "RangeError: la taille maximale de la pile d'appels a été dépassée" lorsque je les utilise.
jcollum
Je peux jeter un œil si vous fournissez votre code sur le Fiddle ou ajoutez un problème sur Github: github.com/Eccenux/JSON-js/issues
Nux
C'est ce que je cherchais. JSON.decycle(a, true)ce qui se passe lorsque vous passez true comme paramètre à la fonction de recyclage.
Rudra
@Rudra true rend l' stringifyNodesoption vraie dans le fork. Cela videra par exemple divavec id = « some-id » à la chaîne: div#some-id. Vous éviterez certains problèmes, mais vous ne pourrez pas effectuer de rétro-cycle complet.
Nux
Il existe un package npm npmjs.com/package/json-js , mais il n'a pas été mis à jour depuis un certain temps
Michael Freidgeim
23

Je recommanderais de vérifier json-stringify-safe de @ isaacs - il est utilisé dans NPM.

BTW- si vous n'utilisez pas Node.js, vous pouvez simplement copier et coller les lignes 4-27 de la partie appropriée du code source .

À installer:

$ npm install json-stringify-safe --save

Utiliser:

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

Cela donne:

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

Notez que, tout comme avec la fonction vanilla JSON.stringify comme @Rob W l'a mentionné, vous pouvez également personnaliser le comportement de désinfection en passant une fonction "replacer" comme deuxième argument stringify(). Si vous avez besoin d'un exemple simple de la façon de procéder, je viens d'écrire un remplaçant personnalisé qui contraint les erreurs, les expressions régulières et les fonctions en chaînes lisibles par l'homme ici .

mikermcneil
la source
13

Pour les futurs googleurs à la recherche d'une solution à ce problème lorsque vous ne connaissez pas les clés de toutes les références circulaires, vous pouvez utiliser un wrapper autour de la fonction JSON.stringify pour exclure les références circulaires. Voir un exemple de script sur https://gist.github.com/4653128 .

La solution se résume essentiellement à conserver une référence aux objets précédemment imprimés dans un tableau et à vérifier cela dans une fonction de remplacement avant de renvoyer une valeur. Il est plus contraignant que d'exclure uniquement les références circulaires, car il exclut également d'imprimer un objet deux fois, l'un des effets secondaires étant d'éviter les références circulaires.

Exemple de 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);
}
Trindaz
la source
3
Beau code. Cependant, vous avez une erreur stupide, vous écrivez if(printedObjIndex)pendant que vous devez écrire, if(printedObjIndex==false)car indexpeut également être 0traduit en, falsesauf indication contraire explicite.
guy mograbi
1
@guymograbi Vous ne voulez pas dire ===? 0 == falseest true, 0 === falseest false. ; ^) Mais je préfère ne pas initialiser printedObjIndexà false, car alors vous pouvez vérifier undefinedsi vous (enfin, Trindaz) ne mélange pas les métaphores aussi étrangement.
ruffin
@ruffin nice catch. oui évidemment, utilisez toujours l'égalité dure et jshint pour attraper ces erreurs idiotes.
guy mograbi
4

Utilisez la méthode JSON.stringify avec un remplaçant. Lisez cette documentation pour plus d'informations. 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));

Trouvez un moyen de remplir le tableau de remplacement avec des références cycliques. Vous pouvez utiliser la méthode typeof pour rechercher si une propriété est de type 'objet' (référence) et une vérification d'égalité exacte (===) pour vérifier la référence circulaire.

TWickz
la source
4
Cela peut uniquement fonctionner dans IE (compte tenu du fait que MSDN est une documentation de Microsoft et que Microsoft crée IE). Dans Firefox / Chrome, jsfiddle.net/ppmaW génère l'erreur de référence circulaire. FYI: var obj = {foo:obj}ne crée pas de référence circulaire. Au lieu de cela, il crée un objet dont l' fooattribut fait référence à la valeur précédente de obj( undefineds'il n'est pas précédemment défini, déclaré à cause de var obj).
Rob W
4

Si

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

résulte en un

TypeError: valeur d'objet cyclique

Ensuite, vous voudrez peut-être imprimer comme ceci:

var output = '';
for (property in object) {
  output += property + ': ' + object[property]+'; ';
}
console.log(output);
Thorsten Niehues
la source
21
Peut-être parce qu'il n'imprime qu'un niveau?
Alex Turpin
TRÈS SIMPLE j'ai voté pour cela car cela a fonctionné pour moi dès la sortie de la boîte en chrome. EXCELLENT
Amour et paix - Joe Codeswell
4
var a={b:"b"};
a.a=a;
JSON.stringify(preventCircularJson(a));

évalue à:

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

avec la fonction:

/**
 * 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;
}
eshalev
la source
3

Je sais que c'est une vieille question, mais je voudrais suggérer un package NPM que j'ai créé appelé smart-circulaire , qui fonctionne différemment des autres méthodes proposées. C'est particulièrement utile si vous utilisez des objets gros et profonds .

Certaines fonctionnalités sont:

  • Remplacement des références circulaires ou des structures simplement répétées à l'intérieur de l'objet par le chemin menant à sa première occurrence (pas seulement la chaîne [circulaire] );

  • En recherchant des circularités dans une recherche en largeur, le package garantit que ce chemin est aussi petit que possible, ce qui est important lorsqu'il s'agit d'objets très gros et profonds, où les chemins peuvent devenir ennuyeusement longs et difficiles à suivre (le remplacement personnalisé dans JSON.stringify fait un DFS);

  • Permet des remplacements personnalisés, pratiques pour simplifier ou ignorer les parties moins importantes de l'objet;

  • Enfin, les chemins sont écrits exactement de la manière nécessaire pour accéder au champ référencé, ce qui peut vous aider à déboguer.

Danilo Augusto
la source
3

Le deuxième argument de JSON.stringify () vous permet également de spécifier un tableau de noms de clés qui doivent être préservés de chaque objet qu'il rencontre dans vos données. Cela peut ne pas fonctionner pour tous les cas d'utilisation, mais c'est une solution beaucoup plus simple.

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

Remarque: Étrangement, la définition d'objet d'OP ne génère pas d'erreur de référence circulaire dans le dernier Chrome ou Firefox. La définition de cette réponse a été modifiée de sorte qu'il a fait une erreur.


Aaron Cicali
la source
Cette réponse devrait être acceptée
Manic Depression
2

Pour mettre à jour la réponse de surcharger le fonctionnement de JSON (probablement non recommandé, mais super simple), n'utilisez pas circular-json(c'est obsolète). Utilisez plutôt le successeur, aplati:

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

Emprunté de l'ancienne réponse ci-dessus de @ user1541685, mais remplacé par la nouvelle:

npm i --save flatted

puis dans votre fichier js

const CircularJSON = require('flatted');
const json = CircularJSON.stringify(obj);
dylanh724
la source
1

J'ai trouvé la bibliothèque circular-json sur github et cela a bien fonctionné pour mon problème.

Quelques bonnes fonctionnalités que j'ai trouvées utiles:

  • Prend en charge l'utilisation multi-plateforme, mais je ne l'ai testé que jusqu'à présent avec node.js.
  • L'API est la même, il vous suffit donc de l'inclure et de l'utiliser comme remplacement JSON.
  • Il a sa propre méthode d'analyse afin que vous puissiez reconvertir les données sérialisées «circulaires» en objet.
JacopKane
la source
2
Cette bibliothèque a généré une erreur pour moi, je dois donc en chercher une autre. ERREUR TypeError: toISOString n'est pas une fonction à String.toJSON (<anonymous>) à Object. <anonymous> ( localhost: 8100 / build / polyfills.js: 1: 3458 ) à JSON.stringify (<anonymous>) à Object. stringifyRecursion [as stringify] ( localhost: 8100 / build / main.js: 258450: 15 )
Mark Ellul
1
@MarkEllul J'ai écrit le commentaire en 2015 et si je vois une meilleure alternative, je le posterai ici avec une modification. Je rencontre toujours le même problème dans le travail quotidien et je préfère généralement mes propres fonctions manuelles de manière récursive avec une inspection appropriée / sûre. Je suggérerais de vérifier les pratiques de programmation fonctionnelle si vous n'êtes pas familier, en général, cela facilite ce type d'opérations récursives comme étant moins compliqué et plus fiable.
JacopKane
Obtenir également "toISOString n'est pas une fonction" essaie de filtrer un événement et de le renvoyer dans un test de cyprès
Devin G Rhode
1

Je résous ce problème comme ceci:

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));
MiF
la source
Cela a plutôt bien fonctionné pour moi, mais il semble que les classes étaient représentées _class: ClassName { data: "here" }, j'ai donc ajouté la règle suivante .replace(/(\w+) {/g, '{ __ClassName__: "$1", '). Dans mon cas, j'essayais de voir à quoi ressemblait un objet de requête http.
redbmk
1

Je sais que cette question est ancienne et a beaucoup de bonnes réponses, mais je poste cette réponse en raison de sa nouvelle saveur (es5 +)

Morteza Tourani
la source
1

Bien que cela ait été répondu suffisamment, vous pouvez également supprimer explicitement la propriété en question avant la stringification en utilisant l' deleteopérateur.

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

supprimer l'opérateur

cela supprimera la nécessité de créer ou de maintenir une logique complexe pour supprimer les références circulaires.

lachlan.p.jordan
la source
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');
}
Sergey Gurin
la source
0

une autre solution pour résoudre ce problème avec ce type d'objets est que l'utilisation de cette bibliothèque

https://github.com/ericmuyser/stringy

c'est simple et vous pouvez en quelques étapes simples résoudre ce problème.

Ehsan Aghaei
la source
0

Sur la base des autres réponses, je me retrouve avec le code suivant. Cela fonctionne assez bien avec des références circulaires, des objets avec des constructeurs personnalisés.

De l'objet donné à sérialiser,

  • Mettez en cache tous les objets que vous rencontrez lors de la traversée de l'objet et attribuez à chacun d'eux un hashID unique (un numéro à incrémentation automatique fonctionne également)
  • Une fois qu'une référence circulaire est trouvée, marquez ce champ dans le nouvel objet comme circulaire et stockez le hashID de l'objet d'origine comme attribut.

Lien Github - 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;

Exemple d'utilisation 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));

Exemple d'utilisation 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));
bytestorm
la source
0

Essaye ça:

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);
IamMHussain
la source
Ne devrait-il pas y avoir, comme, quelques lignes de code supplémentaires après le seen.push(value)= -D? Commefor (var key in value) {value[key] = circular_replacer(value[key]);}
Klesun
Les réponses uniquement codées sont déconseillées. Veuillez cliquer sur modifier et ajouter quelques mots résumant la façon dont votre code répond à la question, ou peut-être expliquer en quoi votre réponse diffère de la ou des réponses précédentes. De l'avis
Nick
0

Dans ma solution, si vous rencontrez un cycle, il ne dit pas seulement "cycle" (ou rien), il dit quelque chose comme foo: voir l'objet n ° 42 ci-dessus, et pour voir où foo pointe vers vous, faites défiler vers le haut et recherchez pour l'objet # 42 (chaque objet, quand il démarre, dit l'objet # xxx avec un entier xxx)

Fragment:

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

mathheadinclouds
la source