Serializar objeto que contiene un valor cíclico

151

Tengo un objeto (árbol de análisis) que contiene nodos secundarios que son referencias a otros nodos.

Me gustaría serializar este objeto, usando JSON.stringify(), pero obtengo

TypeError: valor de objeto cíclico

por las construcciones que mencioné.

¿Cómo podría solucionar esto? No me importa si estas referencias a otros nodos están representadas o no en el objeto serializado.

Por otro lado, eliminar estas propiedades del objeto cuando se crean parece tedioso y no quisiera hacer cambios en el analizador (narciso).

Loos Duros
fuente
1
No podemos ayudarlo sin algún código. Publique los bits relevantes de su objeto y / o salida JSON junto con el JS que utiliza para serializarlo.
Bojangles
1
¿puede agregar algún prefijo a esas propiedades que son referencias internas?
wheresrhys
@Loic Sería valioso tener Douglas Crockford's cycle.jscomo respuesta aquí, ya que es la solución más adecuada para muchos casos. Parece apropiado que publiques esa respuesta, ya que eres el primero en hacer referencia a ella (en tu comentario a continuación). Si no tiene ganas de publicarlo como respuesta, eventualmente lo haré.
Jeremy Banks
1
Desearía que JSON fuera más inteligente o una forma más fácil de resolver esto. Las soluciones son demasiado problemáticas para propósitos de depuración simples (!) Imo.
BluE

Respuestas:

220

Use el segundo parámetro de stringify, la función de reemplazo , para excluir objetos ya serializados:

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/

Como se señaló correctamente en otros comentarios, este código elimina todos los objetos "vistos", no solo los "recursivos".

Por ejemplo, para:

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

El resultado será incorrecto. Si su estructura es así, es posible que desee utilizar el decícleo de Crockford o esta función (más simple) que simplemente reemplaza las referencias recursivas con nulos:

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
fuente
3
aaah bonito! Gracias, voy a intentar esto. Encontré una solución creada por Douglas Crockford ( github.com/douglascrockford/JSON-js/blob/master/cycle.js ), pero como no estoy seguro de la licencia que la acompaña, ¡la solución fácil que describas sería perfecta!
Loic Duros
3
@LoicDuros La licencia es de "dominio público". Es decir, puedes hacer lo que quieras con él.
Ates Goral
1
este código produce bucles de ciclismo, tenga cuidado con el uso, muy potencial bloquea su aplicación. necesita puntos y comas correctos y no se puede usar en objetos de evento.
Ol Sen
3
Esto elimina más que solo referencias cíclicas: simplemente elimina todo lo que aparece más de una vez. A menos que el objeto que ya ha sido serializado sea un "padre" del nuevo objeto, no debe eliminarlo
Gio
1
¡Buena respuesta! Modifiqué esto un poco, cambié la función a una función recursiva, para que los objetos secundarios se clonaran de la forma en que se clonan los objetos principales.
HoldOffHunger
2

Creé un GitHub Gist que es capaz de detectar estructuras cíclicas y también descifrarlas y codificarlas: https://gist.github.com/Hoff97/9842228

Para transformar simplemente use JSONE.stringify / JSONE.parse. También des y codifica funciones. Si desea desactivar esto, simplemente elimine las líneas 32-48 y 61-85.

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

Puede encontrar un ejemplo de violín aquí:

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

Hoff
fuente
2

Esta es una especie de respuesta alternativa, pero dado que muchas personas vendrán aquí para depurar sus objetos circulares y no hay realmente una excelente manera de hacerlo sin extraer un montón de código, aquí va.

Una característica que no es tan conocida como JSON.stringify()es console.table(). Simplemente llame console.table(whatever);, y registrará la variable en la consola en formato tabular, por lo que es bastante fácil y conveniente examinar detenidamente el contenido de la variable.

Andrés
fuente
1

mucho ahorro y muestra dónde estaba un objeto de ciclo .

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

produce

jsonify = {"g":{"d":[2,5],"j":2},"e":10,"someloopshere":[{"d":[2,5],"j":2},"__cycle__",{"a":[10,"__cycle__"]}]}
Ol Sen
fuente
pero todavía hay un problema con este código si sería bueno ver a alguien construir un objeto obj.b=this'si alguien sabe cómo evitar cálculos muy largos hechos con un alcance incorrecto dadothis
Ol Sen
2
Esto debería serseen.indexOf(v) != -1
1

También creo un proyecto github que puede serializar objetos cíclicos y restaurar la clase si lo guarda en el atributo serializename como una Cadena

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

Editar: he transformado mi script para NPM https://github.com/bormat/borto_circular_serialize y he cambiado los nombres de las funciones de francés a inglés.

bormat
fuente
Este ejemplo no se ajusta a la esencia. The Gist tiene errores.
Ernst Ernst
Buena idea, pero una vez que esté listo :-) Si lo hiciera distribuido en npm, tal vez desarrollaría incluso tipificaciones para eso, probablemente se hizo bastante popular.
peterh - Restablece a Monica
1

Aquí hay un ejemplo de una estructura de datos con referencias cíclicas: toolhedCY

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

Cuando desee MANTENER las referencias cíclicas (restaurarlas cuando deserialice, en lugar de "destruirlas"), tiene 2 opciones, que compararé aquí. Primero es el ciclo.js de Douglas Crockford , segundo es mi paquete de siberia . Ambos trabajan primero "desciclando" el objeto, es decir, construyendo otro objeto (sin ninguna referencia cíclica) "que contenga la misma información".

Crockford va primero:

JSON.decycle(makeToolshed())

JSON_decycleMakeToolshed

Como puede ver, la estructura anidada de JSON se conserva, pero hay una cosa nueva, que son los objetos con la $refpropiedad especial . Veamos cómo funciona eso.

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

El signo de dólar representa la raíz. .boltTener $refnos dice que .boltes un objeto "ya visto", y el valor de esa propiedad especial (aquí, la cadena $ ["tuerca"] ["necesita"]) nos dice dónde, ver primero ===arriba. Asimismo para el segundo $refy el segundo ===arriba.

Usemos una prueba de igualdad profunda adecuada (es decir, la deepGraphEqualfunción de Anders Kaseorg de la respuesta aceptada a esta pregunta ) para ver si la clonación funciona.

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

Ahora, siberia:

JSON.Siberia.forestify(makeToolshed())

JSON_Siberia_forestify_makeToolshed

Siberia no intenta imitar JSON "clásico", no hay estructura anidada. El gráfico del objeto se describe de manera "plana". Cada nodo del gráfico de objeto se convierte en un árbol plano (lista de pares de valores de clave simple con valores enteros), que es una entrada en .forest.En el índice cero, encontramos el objeto raíz, en índices más altos, encontramos los otros nodos de el gráfico de objetos y los valores negativos (de alguna clave de algún árbol del bosque) apuntan a la atomsmatriz (que se escribe a través de la matriz de tipos, pero omitiremos los detalles de escritura aquí). Todos los nodos terminales están en la tabla de átomos, todos los nodos no terminales están en la tabla del bosque, y puede ver de inmediato cuántos nodos tiene el gráfico de objeto, a saber forest.length. Probemos si funciona:

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

comparación

agregará la sección más tarde.

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

Faltaba una condición previa; de lo contrario, los valores enteros en los objetos de matriz se truncan, es decir, [[08.11.2014 12:30:13, 1095]] 1095 se reduce a 095.

usuario3893329
fuente
obteniendo RefrenceError: No se puede encontrar la variable: _
amit pandya
Por favor arregle su código.
Anastasios Moraitis