JSON.stringify, éviter TypeError: convertir la structure circulaire en JSON

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

Merci.

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

Je souhaite stringify obj dans:

 {"a":"foo"} 

Utilisez JSON.stringify avec un remplacement personnalisé. Par exemple:

 // Demo: Circular reference var o = {}; oo = o; // Note: cache should not be re-used by repeated calls to JSON.stringify. var cache = []; JSON.stringify(o, function(key, value) { if (typeof value === 'object' && value !== null) { if (cache.indexOf(value) !== -1) { // Circular reference found, discard key return; } // Store value in our collection cache.push(value); } return value; }); cache = null; // Enable garbage collection 

Le remplacement dans cet exemple n'est pas 100% correct (selon votre définition de «duplication»). Dans le cas suivant, une valeur est rejeté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 repose: utilisez un remplacement personnalisé et faites le suivi des valeurs d'objet analysées.

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

J'ai vraiment aimé la solution de Trindaz – plus prononcée, mais elle avait des bugs. Je les ai réparés pour ceux qui l'aiment aussi.

De plus, j'ai ajouté une limite de longueur sur 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); }; 

Notez qu'il existe également une méthode JSON.decycle mise en œuvre par Douglas Crockford. Voir son cycle.js . Cela vous permet de trier 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 original avec retrocycle méthode de retrocycle . Donc, vous ne devez pas supprimer les cycles des objets pour les classer.

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

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

Je recommande de vérifier json-stringify-safe à partir 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 pertinente 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 cède:

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

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

Pour les futurs googlers à 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 à l' adresse https://gist.github.com/4653128 .

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

Exemple d'emballage:

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

Faites simplement

 npm i --save circular-json 

Puis dans votre fichier js

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

Vous pourriez aussi faire

 const CircularJSON = require('circular-json'); 

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

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

Utilisez la méthode JSON.stringify avec un remplacement. 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 peupler le réseau de remplacement avec des références cycliques. Vous pouvez utiliser la méthode typeof pour trouver 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.

 var a={b:"b"}; aa=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; } 

Je sais que c'est une ancienne question, mais j'aimerais proposer un paquet NPM que j'ai créé appelé smart-circular , qui fonctionne différemment des autres méthodes proposées. Il est particulièrement utile si vous utilisez des objets grands et profonds .

Certaines fonctionnalités sont:

  • Remplacer les références circulaires ou simplement les structures 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 première recherche, le paquet garantit que ce chemin est aussi petit que possible, ce qui est important lorsqu'il s'agit d'objets très grands et profonds, où les chemins peuvent devenir ennuyeux 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.

Je résolvez 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)); 

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

 Object.defineProperties(JSON, { refStringify: { value: function(obj) { let objMap = new Map(); let stringified = JSON.stringify(obj, function(key, value) { // only for objects if (typeof value == 'object') { // If has the value then return a reference to it if (objMap.has(value)) return objMap.get(value); objMap.set(value, `ref${objMap.size + 1}`); } return value; }); return stringified; } }, refParse: { value: function(str) { let parsed = JSON.parse(str); let objMap = _createObjectMap(parsed); objMap.forEach((value, key) => _replaceKeyWithObject(value, key)); return parsed; } }, }); // *************************** Example let a = { b: 32, c: { get a() { return a; }, get c() { return ac; } } }; let stringified = JSON.refStringify(a); let parsed = JSON.refParse(stringified, 2); console.log(parsed, JSON.refStringify(parsed)); // *************************** /Example // *************************** Helper function _createObjectMap(obj) { let objMap = new Map(); JSON.stringify(obj, (key, value) => { if (typeof value == 'object') { if (objMap.has(value)) return objMap.get(value); objMap.set(value, `ref${objMap.size + 1}`); } return value; }); return objMap; } function _replaceKeyWithObject(key, obj, replaceWithObject = obj) { Object.keys(obj).forEach(k => { let val = obj[k]; if (val == key) return (obj[k] = replaceWithObject); if (typeof val == 'object' && val != replaceWithObject) _replaceKeyWithObject(key, val, replaceWithObject); }); } 

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

J'ai trouvé la bibliothèque circulaire Json sur github et ça a bien fonctionné pour mon problème.

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

  • Prise en charge de l'utilisation multi-plate-forme, mais je l'ai seulement testé avec node.js jusqu'à présent.
  • L'API est identique, donc tout ce que vous devez faire est d'inclure et de l'utiliser comme un remplacement JSON.
  • Il a sa propre méthode d'analyse afin que vous puissiez convertir les données en série "circulaires" en objet.

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

De l'objet donné à sérialiser,

  • Cachez l'objet que vous rencontrez tout en traversant l'objet et attribuez à chacun un hachage unique (un nombre d'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 enregistrez le hashID de l'objet d'origine comme un attribut.

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; 

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

Si

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

Entraîne une

TypeError: valeur d'objet cyclique

Ensuite, vous pouvez imprimer comme ceci:

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