Objet de sérialisation contenant une valeur d'objet cyclique

151

J'ai un objet (arbre d'analyse) qui contient des nœuds enfants qui sont des références à d'autres nœuds.

Je voudrais sérialiser cet objet, en utilisant JSON.stringify(), mais j'obtiens

TypeError: valeur de l'objet cyclique

à cause des constructions que j'ai mentionnées.

Comment pourrais-je contourner ce problème? Peu m'importe que ces références à d'autres nœuds soient représentées ou non dans l'objet sérialisé.

D'autre part, supprimer ces propriétés de l'objet lors de leur création semble fastidieux et je ne voudrais pas apporter de modifications à l'analyseur (narcisse).

Loic Duros
la source
1
Nous ne pouvons pas vous aider sans un code. Veuillez publier les bits pertinents de votre objet et / ou de la sortie JSON avec le JS que vous utilisez pour le sérialiser.
Bojangles
1
pouvez-vous ajouter un préfixe à ces propriétés qui sont des références internes?
wheresrhys
@Loic Il serait intéressant d'avoir Douglas Crockford cycle.jscomme réponse ici, car c'est la solution la plus appropriée dans de nombreux cas. Il vous semble approprié de publier cette réponse, puisque vous êtes le premier à la référencer (dans votre commentaire ci-dessous). Si vous n'avez pas envie de l'afficher vous-même comme réponse, je le ferai éventuellement.
Jeremy Banks
2
Double
user3791372
1
Je souhaite que JSON soit plus intelligent ou un moyen plus simple de résoudre ce problème. Les solutions sont trop gênantes pour de simples (!) Fins de débogage imo.
BluE

Réponses:

220

Utilisez le deuxième paramètre de stringify, la fonction de remplacement , pour exclure les objets déjà sérialisés:

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/

Comme indiqué correctement dans d'autres commentaires, ce code supprime tous les objets "vus", pas seulement ceux "récursifs".

Par exemple, pour:

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

le résultat sera incorrect. Si votre structure est comme ça, vous voudrez peut-être utiliser le decycle de Crockford ou cette fonction (plus simple) qui remplace simplement les références récursives par des valeurs nulles:

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

Georg
la source
3
aaah sympa! Merci, je vais essayer ça. J'ai trouvé une solution créée par Douglas Crockford ( github.com/douglascrockford/JSON-js/blob/master/cycle.js ), mais comme je ne suis pas sûr de la licence qui va avec, la solution simple que vous décrivez serait parfaite!
Loic Duros
3
@LoicDuros La licence est "domaine public". Cela signifie que vous pouvez en faire tout ce que vous voulez.
Ates Goral
1
ce code produit des boucles de cyclisme, attention à l'utilisation, des plantages très potentiels de votre application. a besoin de points-virgules corrects et n'est pas utilisable sur les objets événement!
Ol Sen
3
Cela supprime plus que de simples références cycliques - cela supprime simplement tout ce qui apparaît plus d'une fois. Sauf si l'objet qui a déjà été sérialisé est un "parent" du nouvel objet, vous ne devriez pas le supprimer
Gio
1
Bonne réponse! J'ai un peu modifié cela, changé la fonction en une fonction récursive, afin que les objets enfants soient clonés de la manière dont les objets parents sont clonés.
HoldOffHunger
2

J'ai créé un GitHub Gist qui est capable de détecter les structures cycliques et également de les décoder et de les encoder: https://gist.github.com/Hoff97/9842228

Pour transformer, utilisez simplement JSONE.stringify / JSONE.parse. Il dé- et encode également les fonctions. Si vous souhaitez désactiver cela, supprimez simplement les lignes 32-48 et 61-85.

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

Vous pouvez trouver un exemple de violon ici:

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

Hoff
la source
2

C'est une sorte de réponse alternative, mais comme beaucoup de gens viendront ici pour déboguer leurs objets circulaires et qu'il n'y a pas vraiment un excellent moyen de le faire sans extraire un tas de code, voilà.

Une fonctionnalité qui n'est pas aussi connue qu'elle l' JSON.stringify()est console.table(). Appelez simplement console.table(whatever);, et il enregistrera la variable dans la console au format tabulaire, ce qui rend assez facile et pratique de parcourir le contenu de la variable.

Andrew
la source
1

beaucoup plus économique et il montre où se trouvait un objet de cycle .

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

produit

jsonify = {"g":{"d":[2,5],"j":2},"e":10,"someloopshere":[{"d":[2,5],"j":2},"__cycle__",{"a":[10,"__cycle__"]}]}
Ol Sen
la source
mais il y a toujours un problème avec ce code si quelqu'un construirait un objet avec obj.b=this'si quelqu'un sait comment empêcher de très longs calculs faits d'une mauvaise portée donnée avec ce thisserait bien de voir ici
Ol Sen
2
Cela devrait êtreseen.indexOf(v) != -1
1

Je crée aussi un projet github qui peut sérialiser l'objet cyclique et restaurer la classe si vous l'enregistrez dans l'attribut serializename comme une chaîne

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

Edit: J'ai transformé mon script pour NPM https://github.com/bormat/borto_circular_serialize et j'ai changé les noms de fonctions du français vers l'anglais.

bormat
la source
Cet exemple ne correspond pas au Gist. Le Gist a des erreurs.
Ernst Ernst
Bonne idée - mais une fois préparez-le :-) Si vous le faisiez distribuer dans npm, peut-être que vous développeriez même des typages pour cela, il est probablement devenu très populaire.
peterh - Réintégrer Monica le
1

Voici un exemple de structure de données avec des références cycliques: toolhedCY

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

Lorsque vous souhaitez GARDER les références cycliques (les restaurer lorsque vous les désérialisez, au lieu de les "nuking"), vous avez 2 choix, que je vais comparer ici. Le premier est cycle.js de Douglas Crockford , le second est mon paquet siberia . Les deux fonctionnent en «décyclant» d'abord l'objet, c'est-à-dire en construisant un autre objet (sans aucune référence cyclique) «contenant les mêmes informations».

M. Crockford commence par:

JSON.decycle(makeToolshed())

JSON_decycleMakeToolshed

Comme vous le voyez, la structure imbriquée de JSON est conservée, mais il y a une nouvelle chose, qui est des objets avec la $refpropriété spéciale . Voyons comment cela fonctionne.

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

Le signe dollar représente la racine. .boltavoir $refnous dit que .boltc'est un objet "déjà vu", et la valeur de cette propriété spéciale (ici, la chaîne $ ["nut"] ["needs"]) nous indique où, voir d'abord ===ci-dessus. De même pour le deuxième $refet le deuxième ===ci-dessus.

Utilisons un test d'égalité profonde approprié (à savoir la deepGraphEqualfonction d' Anders Kaseorg issue de la réponse acceptée à cette question ) pour voir si le clonage fonctionne.

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

Maintenant, la Sibérie:

JSON.Siberia.forestify(makeToolshed())

JSON_Siberia_forestify_makeToolshed

Siberia n'essaye pas d'imiter le JSON "classique", pas de structure imbriquée. Le graphe d'objets est décrit de manière "plate". Chaque nœud du graphe d'objet est transformé en un arbre plat (liste de paires clé / valeur simple avec des valeurs entières uniquement), qui est une entrée dans .forest.À l'index zéro, nous trouvons l'objet racine, à des indices plus élevés, nous trouvons les autres nœuds de le graphe d'objet et les valeurs négatives (d'une clé d'un arbre de la forêt) pointent vers le atomstableau, (qui est tapé via le tableau des types, mais nous ignorerons les détails de saisie ici). Tous les nœuds terminaux sont dans la table des atomes, tous les nœuds non terminaux sont dans la table de la forêt, et vous pouvez voir immédiatement le nombre de nœuds du graphe d'objets, à savoir forest.length. Testons si cela fonctionne:

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

Comparaison

ajoutera une section plus tard.

mathheads
la source
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
    }
  );
}

Une condition préalable était manquante, sinon les valeurs entières dans les objets du tableau sont tronquées, c'est-à-dire que [[08.11.2014 12:30:13, 1095]] 1095 est réduit à 095.

user3893329
la source
obtention de RefrenceError: Impossible de trouver la variable: _
amit pandya
Veuillez corriger votre code.
Anastasios Moraitis