J'ai un gros objet que je veux convertir en JSON et envoyer. Cependant, il a une structure circulaire. Je veux jeter 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 structurer obj en:
{"a":"foo"}
Utilisez JSON.stringify
avec un remplaçant personnalisé. Par exemple:
// Demo: Circular reference
var o = {};
o.o = 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) {
// Duplicate reference found
try {
// If this value does not reference a parent it can be deduped
return JSON.parse(JSON.stringify(value));
} catch (error) {
// discard key if value cannot be deduped
return;
}
}
// Store value in our collection
cache.Push(value);
}
return value;
});
cache = null; // Enable garbage collection
Le remplacement dans cet exemple n'est pas correct à 100% (selon votre définition de "dupliquer"). 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 valable: utilisez un remplaçant personnalisé et suivez les valeurs des objets analysés.
Dans Node.js, vous pouvez utiliser util.inspect (object) . Il remplace automatiquement les liens circulaires par "[Circulaire]".
Bien que 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')
console.log(util.inspect(myObject))
Sachez également que vous pouvez passer l'objet 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 ...
fais juste
npm i --save circular-json
puis dans votre fichier js
const JSON = require('circular-json');
...
const json = JSON.stringify(obj);
Tu pourrais aussi faire
const CircularJSON = require('circular-json');
https://github.com/WebReflection/circular-json
NOTE: je n’ai rien à voir avec ce paquet. Mais je l'utilise pour cela.
J'ai vraiment aimé la solution de Trindaz - plus verbeuse, mais il y avait quelques bugs. Je les ai corrigés pour ceux qui aiment aussi.
De plus, j'ai ajouté une limite de longueur à mes objets en cache.
Si l'objet que j'imprime est vraiment gros - 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
implémentée par Douglas Crockford. Voir son cycle.js . Cela vous permet de structurer presque toutes les structures standard:
var a = [];
a[0] = a;
a[1] = 123;
console.log(JSON.stringify(JSON.decycle(a)));
// result: '[{"$ref":"$"},123]'.
Vous pouvez également recréer l'objet d'origine avec la méthode retrocycle
. Il n'est donc pas nécessaire de supprimer les cycles des objets pour les renforcer.
Cependant, cela pas fonctionnera pour les nœuds DOM (qui sont la cause typique des cycles dans les cas d'utilisation réels). Par exemple, cela jettera:
var a = [document.body];
console.log(JSON.stringify(JSON.decycle(a)));
J'ai créé un fork pour résoudre ce problème (voir mon fork cycle.js ) Cela devrait bien fonctionner:
var a = [document.body];
console.log(JSON.stringify(JSON.decycle(a, true)));
Notez que dans ma fourche, JSON.decycle(variable)
fonctionne comme dans l'original et lève une exception lorsque la variable variable
contient des nœuds/éléments DOM.
Lorsque vous utilisez JSON.decycle(variable, true)
, vous acceptez le fait que le résultat ne sera pas réversible (retrocycle ne recrée pas de nœuds DOM). Les éléments DOM doivent toutefois être identifiables dans une certaine mesure. Par exemple, si un élément div
a un identifiant, il sera remplacé par une chaîne "div#id-of-the-element"
.
La réponse de @ RobW est correcte, mais elle est plus performante! 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;
});
};
Je me demande pourquoi personne n'a posté la solution appropriée de la page MDN pour l'instant ...
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 replaceur est appelé sur chaque élément) et il n'est pas nécessaire d'essayer JSON.stringify
chaque élément dans la chaîne menant à une référence circulaire .
Comme dans la réponse acceptée, cette solution supprime toutes les valeurs répétées, pas uniquement les valeurs circulaires. Mais au moins, il n’a pas de complexité exponentielle.
Je vous recommande de vérifier json-stringify-safe de @ isaacs--, 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 donne:
{
a: 'foo',
b: '[Circular]'
}
Notez que, comme avec la fonction Vanilla JSON.stringify mentionnée par @Rob W, vous pouvez également personnaliser le comportement de désinfection en transmettant une fonction "replacer" en tant que second argument à
stringify()
. Si vous avez besoin d'un exemple simple sur la façon de procéder, je viens d'écrire un substitut personnalisé qui convertit les erreurs, les expressions rationnelles et les fonctions en chaînes lisibles par l'homme ici .
Pour les futurs utilisateurs cherchant 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 du JSON. stringify function pour éliminer les références circulaires. Voir un exemple de script sur https://Gist.github.com/4653128 .
La solution consiste essentiellement à conserver une référence à des 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 interdit également d’imprimer un objet deux fois, ce qui a notamment pour effet 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);
}
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;
}
Utilisez la méthode JSON.stringify avec un substitut. 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 renseigner le tableau de remplacement avec des références cycliques. Vous pouvez utiliser la méthode typeof pour rechercher si la propriété est de type 'objet' (référence) et un contrôle d'égalité exact (===) pour vérifier la référence circulaire.
Je sais que c’est une vieille question, mais je voudrais suggérer un paquetage NPM que j’ai créé et appelé smart-circular , qui fonctionne différemment des autres moyens proposés. C'est particulièrement utile si vous utilisez des objets volumineux et profonds.
Certaines fonctionnalités sont:
Remplacement de références circulaires ou simplement de structures répétées à l'intérieur de l'objet par le chemin menant à sa première occurrence (pas seulement la chaîne [circular]);
En recherchant les circularités lors d'une recherche approfondie en largeur, le package garantit que ce chemin est aussi petit que possible, ce qui est important pour les objets très grands et profonds, où les chemins peuvent devenir ennuyeux et difficiles à suivre (le remplacement personnalisé JSON.stringify fait un DFS);
Permet des remplacements personnalisés, pratiques pour simplifier ou ignorer des 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é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));
Basé sur les autres réponses, je me retrouve avec le code suivant. Cela fonctionne plutôt bien avec des références circulaires, des objets avec des constructeurs personnalisés.
De l'objet donné à sérialiser,
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));
une autre solution pour résoudre ce problème avec ce type d’objets est l’utilisation de cette bibliothèque
https://github.com/ericmuyser/stringy
c'est simple et vous pouvez résoudre ce problème en quelques étapes simples.
Bien que la réponse à cette question soit suffisante, vous pouvez également explicitement supprimer la propriété en question avant la chaîne de caractères à l’aide de l’opérateur delete
.
delete obj.b;
const jsonObject = JSON.stringify(obj);
cela éliminera le besoin de créer ou de maintenir une logique complexe pour supprimer les références circulaires.
Pour mettre à jour la réponse de substitution du fonctionnement de JSON (probablement pas recommandé, mais très simple), n'utilisez pas circular-json
(c'est obsolète). Au lieu de cela, utilisez le successeur, plat:
https://www.npmjs.com/package/flatted
Emprunté à l'ancienne réponse ci-dessus auprès de @ user1541685, mais remplacé par la nouvelle:
npm i --save flatted
puis dans votre fichier js
const JSON = require('flatted');
...
const json = JSON.stringify(obj);
Tu pourrais aussi faire
const CircularJSON = require('flatted');
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');
}
Je sais que cette question est ancienne et a beaucoup de bonnes réponses mais je poste cette réponse à cause 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 a.c;
}
}
};
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);
});
}
J'ai trouvé circular-json library sur github et cela a bien fonctionné pour mon problème.
Quelques fonctionnalités intéressantes que j'ai trouvées utiles:
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);
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);