objeto - json.stringify javascript
JSON.stringify, evita TypeError: Convertir estructura circular a JSON (19)
Tengo un objeto grande que quiero convertir a JSON y enviar. Sin embargo, tiene estructura circular. Quiero tirar cualquier referencia circular que exista y enviar cualquier cosa que pueda ser tildada. ¿Cómo puedo hacer eso?
Gracias.
var obj = {
a: "foo",
b: obj
}
Quiero encadenar obj en:
{"a":"foo"}
Basándome en las otras respuestas termino con el siguiente código. Funciona bastante bien con referencias circulares, objetos con constructores personalizados.
Del objeto dado para ser serializado,
- Almacene en caché todo el objeto con el que se encuentre mientras atraviesa el objeto y asigne a cada uno un hashID único (también funciona un número de incremento automático)
- Una vez que se encuentra una referencia circular, marque ese campo en el nuevo objeto como circular y almacene el hashID del objeto original como un atributo.
Enlace 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
/***************************************************************************/
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;
Ejemplo de uso 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));
Ejemplo de uso 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));
El segundo argumento de JSON.stringify () también le permite especificar una matriz de nombres de claves que deben conservarse de cada objeto que encuentre dentro de sus datos. Esto puede no funcionar para todos los casos de uso, pero es una solución mucho más 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"}
Nota: Extrañamente, la definición de objeto de OP no produce un error de referencia circular en el último Chrome o Firefox. La definición en esta respuesta fue modificada para que arrojara un error.
En Node.js, puede usar util.inspect(object) . Reemplaza automáticamente los enlaces circulares con "[Circular]".
Si bien está integrado (no se requiere instalación) , debe importarlo
import * as util from ''util'' // has no default export
import { inspect } from ''util'' // or directly
// or
var util = require(''util'')
Para usarlo, simplemente llame
console.log(util.inspect(myObject))
También tenga en cuenta que puede pasar el objeto de opciones para inspeccionar (vea el enlace arriba)
inspect(myObject[, options: {showHidden, depth, colors, showProxy, ...moreOptions}])
Por favor, lea y felicite a los comentaristas a continuación ...
Encontré la https://github.com/WebReflection/circular-json y funcionó bien para mi problema.
Algunas buenas características que me parecieron útiles:
- Admite el uso multiplataforma, pero solo lo he probado con node.js hasta ahora.
- La API es la misma, por lo que todo lo que necesita hacer es incluirla y usarla como un reemplazo JSON.
- Tiene su propio método de análisis para que pueda convertir los datos serializados ''circulares'' de nuevo a objeto.
La respuesta de @ RobW es correcta, ¡pero esto es más eficaz! Porque usa 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, discard key
return;
}
// Store value in our set
cache.add(value);
}
return value;
});
};
Otra solución para resolver este problema con este tipo de objetos es el uso de esta biblioteca.
https://github.com/ericmuyser/stringy
Es simple y puedes resolverlo en unos pocos pasos.
Para los futuros googlers que buscan una solución a este problema cuando no conoce las claves de todas las referencias circulares, puede usar un envoltorio alrededor de la función JSON.stringify para descartar referencias circulares. Vea un script de ejemplo en https://gist.github.com/4653128 .
La solución básicamente se reduce a mantener una referencia a objetos previamente impresos en una matriz, y verificar eso en una función de reemplazo antes de devolver un valor. Es más restrictivo que solo descartar referencias circulares, porque también descarta la impresión de un objeto dos veces, uno de los efectos secundarios es evitar las referencias circulares.
Ejemplo de envoltura:
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);
}
Prueba esto:
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);
Realmente me gustó la solución de Trindaz, más detallada, sin embargo, tenía algunos errores. Los arreglé para que a quien le guste también.
Además, agregué un límite de longitud en mis objetos de caché.
Si el objeto que estoy imprimiendo es realmente grande, quiero decir infinitamente grande, quiero limitar mi algoritmo.
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);
};
Recomiendo revisar json-stringify-safe de @ isaacs-- se usa en NPM.
Por cierto, si no estás usando Node.js, puedes copiar y pegar las líneas 4-27 de la parte relevante del código fuente .
Instalar:
$ npm install json-stringify-safe --save
Usar:
// 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));
Esto produce:
{
a: ''foo'',
b: ''[Circular]''
}
Tenga en cuenta que, al igual que con la función vanilla JSON.stringify como se menciona en @Rob W, también puede personalizar el comportamiento de desinfección pasando una función de "reemplazo" como segundo argumento para
stringify()
. Si necesita un ejemplo sencillo de cómo hacerlo, acabo de escribir un sustituto personalizado que obliga a errores, expresiones regulares y funciones en cadenas legibles por humanos here .
Resuelvo este problema así:
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));
Sé que esta es una pregunta antigua, pero me gustaría sugerir un paquete NPM que he creado llamado smart-circular , que funciona de manera diferente a las otras formas propuestas. Es especialmente útil si está utilizando objetos grandes y profundos .
Algunas características son:
Reemplazo de referencias circulares o simplemente estructuras repetidas dentro del objeto por el camino que conduce a su primera aparición (no solo la cadena [circular] );
Al buscar circularidades en una búsqueda de amplitud, el paquete garantiza que este camino sea lo más pequeño posible, lo cual es importante cuando se trata de objetos muy grandes y profundos, donde los caminos pueden ser molestos y difíciles de seguir (el reemplazo personalizado en JSON.stringify hace un DFS);
Permite reemplazos personalizados, útiles para simplificar o ignorar partes menos importantes del objeto;
Finalmente, las rutas se escriben exactamente de la manera necesaria para acceder al campo al que se hace referencia, lo que puede ayudarlo a realizar la depuración.
Sé que esta pregunta es antigua y tiene muchas respuestas geniales, pero publico esta respuesta debido a su nuevo sabor (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);
});
}
Si
console.log(JSON.stringify(object));
resultados en un
TypeError: valor de objeto cíclico
Entonces es posible que desee imprimir así:
var output = '''';
for (property in object) {
output += property + '': '' + object[property]+''; '';
}
console.log(output);
Tenga en cuenta que también hay un método JSON.decycle
implementado por Douglas Crockford. Ver su cycle.js . Esto le permite alinear casi cualquier estructura estándar:
var a = [];
a[0] = a;
a[1] = 123;
console.log(JSON.stringify(JSON.decycle(a)));
// result: ''[{"$ref":"$"},123]''.
También puede recrear el objeto original con el método de retrocycle
. Por lo tanto, no es necesario eliminar los ciclos de los objetos para alinearlos.
Sin embargo, esto no funcionará para los Nodos DOM (que son la causa típica de los ciclos en casos de uso de la vida real). Por ejemplo esto lanzará:
var a = [document.body];
console.log(JSON.stringify(JSON.decycle(a)));
He hecho un tenedor para resolver ese problema (ver mi tenedor de cycle.js ). Esto debería funcionar bien:
var a = [document.body];
console.log(JSON.stringify(JSON.decycle(a, true)));
Tenga en cuenta que en mi bifurcación JSON.decycle(variable)
funciona como en el original y lanzará una excepción cuando la variable
contenga nodos / elementos DOM.
Cuando utiliza JSON.decycle(variable, true)
, acepta el hecho de que el resultado no será reversible (el retrociclo no volverá a crear los nodos DOM). Sin embargo, los elementos DOM deben ser identificables en cierta medida. Por ejemplo, si un elemento div
tiene un id, se reemplazará con una cadena "div#id-of-the-element"
.
Utilice JSON.stringify
con un sustituto personalizado. Por ejemplo:
// 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
El sustituto en este ejemplo no es 100% correcto (dependiendo de su definición de "duplicado"). En el siguiente caso, se desecha un valor:
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, ...);
Pero el concepto es válido: utilice un sustituto personalizado y realice un seguimiento de los valores de los objetos analizados.
Utilice el método JSON.stringify con un sustituto. Lea esta documentación para más información. 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));
Descubra una manera de rellenar la matriz de reemplazo con referencias cíclicas. Puede usar el método typeof para encontrar si una propiedad es de tipo ''objeto'' (referencia) y una verificación de igualdad exacta (===) para verificar la referencia circular.
solo haz
npm i --save circular-json
entonces en tu archivo js
const JSON = require(''circular-json'');
...
const json = JSON.stringify(obj);
Tambien podrias hacer
const CircularJSON = require(''circular-json'');
https://github.com/WebReflection/circular-json
NOTA: No tengo nada que ver con este paquete. Pero sí lo uso para esto.
var a={b:"b"};
a.a=a;
JSON.stringify(preventCircularJson(a));
evalúa a:
"{"b":"b","a":"CIRCULAR_REFERENCE_REMOVED"}"
con la función:
/**
* 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;
}