86 votes

Stringifier (convertir en JSON) un objet JavaScript avec une référence circulaire

J'ai une définition d'objet JavaScript qui contient une référence circulaire : elle a une propriété qui fait référence à l'objet parent.

Il possède également des fonctions que je ne souhaite pas voir transmises au serveur. Comment sérialiser et désérialiser ces objets ?

J'ai lu que la meilleure méthode consiste à utiliser le logiciel stringify de Douglas Crockford. Cependant, j'obtiens l'erreur suivante dans Chrome :

TypeError : Conversion d'une structure circulaire en JSON

Le code :

function finger(xid, xparent){
    this.id = xid;
    this.xparent;
    //other attributes
}

function arm(xid, xparent){
    this.id = xid;
    this.parent = xparent;
    this.fingers = [];

    //other attributes

    this.moveArm = function() {
        //moveArm function details - not included in this testcase
        alert("moveArm Executed");
    }
}

 function person(xid, xparent, xname){
    this.id = xid;
    this.parent = xparent;
    this.name = xname
    this.arms = []

    this.createArms = function () {
        this.arms[this.arms.length] = new arm(this.id, this);
    }
}

function group(xid, xparent){
    this.id = xid;
    this.parent = xparent;
    this.people = [];
    that = this;

    this.createPerson = function () {
        this.people[this.people.length] = new person(this.people.length, this, "someName");
        //other commands
    }

    this.saveGroup = function () {
        alert(JSON.stringify(that.people));
    }
}

Il s'agit d'un cas de test que j'ai créé pour cette question. Ce code comporte des erreurs, mais j'ai essentiellement des objets dans des objets, et une référence passée à chaque objet pour montrer quel est l'objet parent lorsque l'objet est créé. Chaque objet contient également des fonctions, que je ne veux pas voir stringifiées. Je veux juste que les propriétés telles que le Person.Name .

Comment sérialiser avant d'envoyer au serveur et désérialiser en supposant que le même JSON est renvoyé ?

133voto

tocker Points 1282

Structure circulaire se produit lorsque vous avez une propriété de l'objet qui est directement l'objet lui-même ( a -> a ) ou indirectement ( a -> b -> a ).

Pour éviter le message d'erreur, indiquez à JSON.stringify ce qu'il doit faire lorsqu'il rencontre une référence circulaire. Par exemple, si une personne pointe vers une autre personne ("parent"), qui peut (ou non) pointer vers la personne d'origine, procédez comme suit :

JSON.stringify( that.person, function( key, value) {
  if( key == 'parent') { return value.id;}
  else {return value;}
})

Le deuxième paramètre de stringify es un fonction de filtrage . Ici, il convertit simplement l'objet référencé en son ID, mais vous êtes libre de faire ce que vous voulez pour briser la référence circulaire.

Vous pouvez tester le code ci-dessus avec ce qui suit :

function Person( params) {
  this.id = params['id'];
  this.name = params['name']; 
  this.father = null;
  this.fingers = [];
  // etc.
}

var me = new Person({ id: 1, name: 'Luke'});
var him = new Person( { id:2, name: 'Darth Vader'});
me.father = him; 
JSON.stringify(me); // so far so good

him.father = me; // time travel assumed :-)
JSON.stringify(me); // "TypeError: Converting circular structure to JSON"
// But this should do the job:
JSON.stringify(me, function( key, value) {
  if(key == 'father') { 
    return value.id;
  } else {
    return value;
  };
});

BTW, je choisirais un nom d'attribut différent de " parent "car il s'agit d'un mot réservé dans de nombreuses langues (et dans DOM). Cela tend à créer de la confusion par la suite...

10voto

Brandon Boone Points 8372

Il apparaît que dojo peut représenter des références circulaires dans JSON sous la forme : {"id":"1","me":{"$ref":"1"}}

En voici un exemple :

http://jsfiddle.net/dumeG/

require(["dojox/json/ref"], function(){
    var me = {
        name:"Kris",
        father:{name:"Bill"},
        mother:{name:"Karen"}
    };
    me.father.wife = me.mother;
    var jsonMe = dojox.json.ref.toJson(me); // serialize me
    alert(jsonMe);
});​

Produit :

{
   "name":"Kris",
   "father":{
     "name":"Bill",
     "wife":{
          "name":"Karen"
      }
   },
   "mother":{
     "$ref":"#father.wife"
   }
}

Note : Vous pouvez également dé-sérialiser ces objets à référence circulaire à l'aide de la fonction dojox.json.ref.fromJson méthode.

Autres ressources :

Comment sérialiser un noeud DOM en JSON même s'il y a des références circulaires ?

JSON.stringify ne peut pas représenter les références circulaires

10voto

Kamil Kiełczewski Points 6496

No-lib

Utiliser ci-dessous remplaçant pour générer du json avec des références de chaînes de caractères (similaire à chemin d'accès json ) aux objets référencés en double/circulaire

let s = JSON.stringify(obj, refReplacer());

function refReplacer() {
  let m = new Map(), v= new Map(), init = null;

  return function(field, value) {
    let p= m.get(this) + (Array.isArray(this) ? `[${field}]` : '.' + field); 
    let isComplex= value===Object(value)

    if (isComplex) m.set(value, p);  

    let pp = v.get(value)||'';
    let path = p.replace(/undefined\.\.?/,'');
    let val = pp ? `#REF:${pp[0]=='[' ? '$':'$.'}${pp}` : value;

    !init ? (init=value) : (val===init ? val="#REF:$" : 0);
    if(!pp && isComplex) v.set(value, path);

    return val;
  }
}

// ---------------
// TEST
// ---------------

// gen obj with duplicate references
let a = { a1: 1, a2: 2 };
let b = { b1: 3, b2: "4" };
let obj = { o1: { o2:  a  }, b, a }; // duplicate reference
a.a3 = [1,2,b];                      // circular reference
b.b3 = a;                            // circular reference

let s = JSON.stringify(obj, refReplacer(), 4);

console.log(s);

Et la fonction d'analyseur syntaxique suivante pour régénérer l'objet à partir de ce "ref-json".

function parseRefJSON(json) {
  let objToPath = new Map();
  let pathToObj = new Map();
  let o = JSON.parse(json);

  let traverse = (parent, field) => {
    let obj = parent;
    let path = '#REF:$';

    if (field !== undefined) {
      obj = parent[field];
      path = objToPath.get(parent) + (Array.isArray(parent) ? `[${field}]` : `${field?'.'+field:''}`);
    }

    objToPath.set(obj, path);
    pathToObj.set(path, obj);

    let ref = pathToObj.get(obj);
    if (ref) parent[field] = ref;

    for (let f in obj) if (obj === Object(obj)) traverse(obj, f);
  }

  traverse(o);
  return o;
}

// ------------
// TEST
// ------------

let s = `{
    "o1": {
        "o2": {
            "a1": 1,
            "a2": 2,
            "a3": [
                1,
                2,
                {
                    "b1": 3,
                    "b2": "4",
                    "b3": "#REF:$.o1.o2"
                }
            ]
        }
    },
    "b": "#REF:$.o1.o2.a3[2]",
    "a": "#REF:$.o1.o2"
}`;

console.log('Open Chrome console to see nested fields:');
let obj = parseRefJSON(s);

console.log(obj);

5voto

nevf Points 254

J'ai trouvé deux modules appropriés pour gérer les références circulaires dans JSON.

  1. CircularJSON https://github.com/WebReflection/circular-json dont la sortie peut être utilisée comme entrée de .parse(). Il fonctionne également avec les navigateurs et Node.js Voir aussi : http://webreflection.blogspot.com.au/2013/03/solving-cycles-recursions-and-circulars.html
  2. Isaacs json-stringify-safe https://github.com/isaacs/json-stringify-safe qui est peut-être plus lisible mais ne peut pas être utilisé pour .parse et n'est disponible que pour Node.js

L'un ou l'autre devrait répondre à vos besoins.

4voto

Matt Evans Points 41

Je suis tombé sur ce fil de discussion parce que j'avais besoin d'enregistrer des objets complexes sur une page, étant donné que le débogage à distance n'était pas possible dans ma situation particulière. J'ai trouvé le propre cycle.js de Douglas Crockford (incepteur de JSON), qui annote les références circulaires comme des chaînes de caractères de manière à ce qu'elles puissent être reconnectées après l'analyse. La copie profonde dé-cyclée est sûre pour passer par JSON.stringify. Bonne lecture !

https://github.com/douglascrockford/JSON-js

cycle.js : Ce fichier contient deux fonctions, JSON.decycle et JSON.retrocycle, qui permettent d'encoder des structures cycliques cycliques en JSON, puis de les récupérer. Il s'agit d'une capacité qui n'est pas fournie par ES5. JSONPath est utilisé pour représenter les liens.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X