2010-07-27 50 views
67

Estoy escribiendo un serializador para serializar POJO a JSON pero atorado en un problema de referencia circular. En la relación hibernate bidireccional de uno a muchos, el padre hace referencia a las referencias hijo e hijo de nuevo al padre y aquí mi serializador muere. (vea el código de ejemplo a continuación)
¿Cómo romper este ciclo? ¿Podemos obtener el árbol propietario de un objeto para ver si el objeto existe en algún lugar de su propia jerarquía de propietarios? ¿Alguna otra forma de encontrar si la referencia va a ser circular? o alguna otra idea para resolver este problema?¿Cómo resolver referencias circulares en el serializador json causado por el mapeo bidireccional de hibernación?

+0

Quizás quiso decir para pegar en algún código para nosotros para ayudar a resolver su problema? – Russell

+2

La solución basada en anotaciones de eugene está bien, pero en este caso no es necesaria una anotación adicional ni la implementación de ExclusionStrategy. Simplemente use la palabra clave Java '[transient] (http://stackoverflow.com/questions/910374/why-does-java-have-transient-variables)' para eso. Funciona para la serialización de objetos Java estándar pero también [Gson lo respeta] (https://sites.google.com/site/gson/gson-user-guide#TOC-Java-Modifier-Exclusion). – MeTTeO

Respuesta

9

¿Puede una relación bi-direccional estar representada en JSON? Algunos formatos de datos no son buenos para algunos tipos de modelado de datos.

Un método para lidiar con ciclos cuando se trata de gráficos de objetos atravesados ​​es hacer un seguimiento de los objetos que has visto hasta ahora (usando comparaciones de identidad), para evitar atravesar un ciclo infinito.

+0

Hice lo mismo y funciona pero no estoy seguro si funcionará en todos los escenarios de mapeo. Al menos por ahora estoy bien configurado y seguiré pensando en tener una idea más elegante – WSK

+0

Por supuesto que pueden, simplemente no hay un tipo de datos nativo o una estructura para esto. Pero cualquier cosa se puede representar en XML, JSON o en la mayoría de los demás formatos de datos. – StaxMan

+4

Tengo curiosidad: ¿cómo representarías una referencia circular en JSON? –

46

que se basan en Google JSON Para hacer frente a este tipo de problema utilizando la función

Excluding Fields From Serialization and Deserialization

Supongamos una relación bidireccional entre A y clase B de la siguiente

public class A implements Serializable { 

    private B b; 

} 

Y B

public class B implements Serializable { 

    private A a; 

} 

Ahora usa GsonBuilder Para obtener un objeto Gson encargo de la siguiente manera (Nótese setExclusionStrategies método)

Gson gson = new GsonBuilder() 
    .setExclusionStrategies(new ExclusionStrategy() { 

     public boolean shouldSkipClass(Class<?> clazz) { 
      return (clazz == B.class); 
     } 

     /** 
      * Custom field exclusion goes here 
      */ 
     public boolean shouldSkipField(FieldAttributes f) { 
      return false; 
     } 

    }) 
    /** 
     * Use serializeNulls method if you want To serialize null values 
     * By default, Gson does not serialize null values 
     */ 
    .serializeNulls() 
    .create(); 

Ahora nuestra referencia circular

A a = new A(); 
B b = new B(); 

a.setB(b); 
b.setA(a); 

String json = gson.toJson(a); 
System.out.println(json); 

Tome un vistazo a GsonBuilder clase

+0

Gracias Arthur por su amable sugerencia, pero la verdadera pregunta es cuál es la mejor manera de construir el llamado método genérico "shouldSkipClass". Por ahora trabajé en la idea de Matt y resolví mi problema, pero todavía escéptico, en el futuro esta solución puede romperse en ciertos escenarios. – WSK

+5

Esto "resuelve" las referencias circulares eliminándolas. No hay forma de reconstruir la estructura de datos original desde el JSON generado. –

33

Jackson 1.6 (lanzado en septiembre de 2010) tiene soporte de anotación específico para mano ling tal vinculación padre/hijo, vea http://wiki.fasterxml.com/JacksonFeatureBiDirReferences. (Wayback Snapshot)

Por supuesto, ya puede excluir la serialización del enlace principal que ya utiliza la mayoría de los paquetes de procesamiento JSON (jackson, gson y flex-json al menos lo admiten), pero el verdadero truco está en cómo deserializarlo (re -crear enlace principal), no solo manejar el lado de serialización. Aunque parece que por ahora solo la exclusión podría funcionar para usted.

EDITAR (abril de 2012): Jackson 2.0 ahora es compatible con true identity references (Wayback Snapshot), por lo que puede resolverlo de esta manera también.

+3

El enlace no funciona. – LOLKFC

+0

cómo hacer que esto funcione cuando la dirección no es siempre la misma ... intenté poner ambas anotaciones en ambos campos pero no funcionó: Clase A { @JsonBackReference ("abc") @JsonManagedReference ("xyz") privado B b; } Clase B { @JsonManagedReference ("ABC") @JsonBackReference ("XYZ") privada A una; } – azi

+0

Como se indicó anteriormente, Object Id (@JsonIdentityInfo) es la forma de hacer que las referencias generales funcionen. Las referencias Administradas/Atrás requieren ciertas instrucciones, por lo que no funcionarán para su caso. – StaxMan

-11

Por ejemplo, ProductBean tiene serialBean. El mapeo sería una relación bidireccional. Si ahora tratamos de usar gson.toJson(), terminará con referencia circular. Para evitar ese problema, puede seguir estos pasos:

  1. Recupere los resultados de la fuente de datos.
  2. Iterar la lista y asegúrese de que el serialBean no es nulo, y luego
  3. Establecer productBean.serialBean.productBean = null;
  4. A continuación, intente utilizar gson.toJson();

Eso debería resolver el problema

12

Al abordar este problema, Tomé el siguiente enfoque (estandarizando el proceso en mi aplicación, haciendo que el código sea claro y reutilizable):

  1. Crear una clase de anotación para ser utilizado en los campos desea excluidos
  2. definir una clase que implementa la interfaz ExclusionStrategy de Google
  3. Crear un método simple para generar el objeto GSON utilizando el GsonBuilder (similar a la explicación de Arthur)
  4. Anotar los campos que ser excluidos según sea necesario
  5. Aplicar las reglas de serialización a su objeto com.google.gson.Gson
  6. Serialice su objeto

Aquí está el código:

1)

import java.lang.annotation.ElementType; 
import java.lang.annotation.Retention; 
import java.lang.annotation.RetentionPolicy; 
import java.lang.annotation.Target; 

@Retention(RetentionPolicy.RUNTIME) 
@Target({ElementType.FIELD, ElementType.METHOD}) 
public @interface GsonExclude { 

} 

2)

import com.google.gson.ExclusionStrategy; 
import com.google.gson.FieldAttributes; 

public class GsonExclusionStrategy implements ExclusionStrategy{ 

    private final Class<?> typeToExclude; 

    public GsonExclusionStrategy(Class<?> clazz){ 
     this.typeToExclude = clazz; 
    } 

    @Override 
    public boolean shouldSkipClass(Class<?> clazz) { 
     return (this.typeToExclude != null && this.typeToExclude == clazz) 
        || clazz.getAnnotation(GsonExclude.class) != null; 
    } 

    @Override 
    public boolean shouldSkipField(FieldAttributes f) { 
     return f.getAnnotation(GsonExclude.class) != null; 
    } 

} 

3)

static Gson createGsonFromBuilder(ExclusionStrategy exs){ 
    GsonBuilder gsonbuilder = new GsonBuilder(); 
    gsonbuilder.setExclusionStrategies(exs); 
    return gsonbuilder.serializeNulls().create(); 
} 

4)

public class MyObjectToBeSerialized implements Serializable{ 

    private static final long serialVersionID = 123L; 

    Integer serializeThis; 
    String serializeThisToo; 
    Date optionalSerialize; 

    @GsonExclude 
    @ManyToOne(fetch=FetchType.LAZY, optional=false) 
    @JoinColumn(name="refobj_id", insertable=false, updatable=false, nullable=false) 
    private MyObjectThatGetsCircular dontSerializeMe; 

    ...GETTERS AND SETTERS... 
} 

5)

En el primer caso, nula se suministra al constructor, puede especificar otra clase que ser excluidos - se añaden las dos opciones a continuación

Gson gsonObj = createGsonFromBuilder(new GsonExclusionStrategy(null)); 
Gson _gsonObj = createGsonFromBuilder(new GsonExclusionStrategy(Date.class)); 

6)

MyObjectToBeSerialized _myobject = someMethodThatGetsMyObject(); 
String jsonRepresentation = gsonObj.toJson(_myobject); 

o, para excluir el objeto Date

String jsonRepresentation = _gsonObj.toJson(_myobject); 
+0

esto evita que el objeto hijo sea procesado, puedo incluir el json hijo y luego detener el ciclo – ir2pid

1

Si está utilizando Javascript , hay una solución muy fácil para eso utilizando el parámetro replacer del método JSON.stringify() donde puede pasar una función para modificar el comportamiento de serialización predeterminado.

Así es como puede usarlo. Considere el siguiente ejemplo con 4 nodos en un gráfico cíclico.

// node constructor 
function Node(key, value) { 
    this.name = key; 
    this.value = value; 
    this.next = null; 
} 

//create some nodes 
var n1 = new Node("A", 1); 
var n2 = new Node("B", 2); 
var n3 = new Node("C", 3); 
var n4 = new Node("D", 4); 

// setup some cyclic references 
n1.next = n2; 
n2.next = n3; 
n3.next = n4; 
n4.next = n1; 

function normalStringify(jsonObject) { 
    // this will generate an error when trying to serialize 
    // an object with cyclic references 
    console.log(JSON.stringify(jsonObject)); 
} 

function cyclicStringify(jsonObject) { 
    // this will successfully serialize objects with cyclic 
    // references by supplying @name for an object already 
    // serialized instead of passing the actual object again, 
    // thus breaking the vicious circle :) 
    var alreadyVisited = []; 
    var serializedData = JSON.stringify(jsonObject, function(key, value) { 
     if (typeof value == "object") { 
      if (alreadyVisited.indexOf(value.name) >= 0) { 
       // do something other that putting the reference, like 
       // putting some name that you can use to build the 
       // reference again later, for eg. 
       return "@" + value.name; 
      } 
      alreadyVisited.push(value.name); 
     } 
     return value; 
    }); 
    console.log(serializedData); 
} 

Más tarde, puede fácilmente volver a crear el objeto real con las referencias cíclicas mediante el análisis de los datos serializados y la modificación de la propiedad next para que apunte al objeto real, si se trata de utilizar una referencia con nombre con un @ como en este ejemplo.

2

usa una solución similar a la de Arthur, pero en lugar de setExclusionStrategies que utilizan

Gson gson = new GsonBuilder() 
       .excludeFieldsWithoutExposeAnnotation() 
       .create(); 

y usados ​​@Expose anotación GSON para los campos que necesito en el JSON, se excluyen otros campos.

+0

Gracias amigo. Esto me funcionó. Después de una larga investigación, finalmente encontré la manera de salir de este error. – kepy97

-3

la respuesta número 8 es la mejor, creo que si sabe qué campo está arrojando un error, solo establece el fild en nulo y resuelto.

List<RequestMessage> requestMessages = lazyLoadPaginated(first, pageSize, sortField, sortOrder, filters, joinWith); 
    for (RequestMessage requestMessage : requestMessages) { 
     Hibernate.initialize(requestMessage.getService()); 
     Hibernate.initialize(requestMessage.getService().getGroupService()); 
     Hibernate.initialize(requestMessage.getRequestMessageProfessionals()); 
     for (RequestMessageProfessional rmp : requestMessage.getRequestMessageProfessionals()) { 
      Hibernate.initialize(rmp.getProfessional()); 
      rmp.setRequestMessage(null); // ** 
     } 
    } 

Para hacer que el código legible por una gran comentario se mueve de la // ** comentario a continuación.

java.lang.StackOverflowError [El proceso de solicitud ha fallado; La excepción anidada es org.springframework.http.converter.HttpMessageNotWritableException: No se pudo escribir JSON: recursión infinita (StackOverflowError) (a través de la cadena de referencia: com.service.pegazo.bo.RequestMessageProfessional ["requestMessage"] -> com.service.pegazo. bo.RequestMessage [ ""] requestMessageProfessionals

+1

No hay una "respuesta número 8", sería mejor dar el nombre del autor de la respuesta a la que se hace referencia. El texto que publicaste aquí no se puede leer, mira cómo se publican las respuestas y trata de diseñarlas correctamente. Finalmente, no entiendo cómo responde esto a la pregunta original. Por favor agregue más detalles para explicar la respuesta. – AdrianHHH

0

este error puede appened cuando se tienen dos objetos:

class object1{ 
    private object2 o2; 
} 

class object2{ 
    private object1 o1; 
} 

Con el uso de GSON para la serialización, tengo este error:

java.lang.IllegalStateException: circular reference error 

Offending field: o1 

Para resolver esto, sólo tiene que añadir la palabra clave transitoria:

class object1{ 
    private object2 o2; 
} 

class object2{ 
    transient private object1 o1; 
} 

Como se puede ver aquí: Why does Java have transient fields?

La palabra clave transitoria en Java se utiliza para indicar que un campo no debe ser serializado.

1

Así es como finalmente lo resolví en mi caso. Esto funciona al menos con Gson & Jackson.

private static final Gson gson = buildGson(); 

private static Gson buildGson() { 
    return new GsonBuilder().addSerializationExclusionStrategy(getExclusionStrategy()).create(); 
} 

private static ExclusionStrategy getExclusionStrategy() { 
    ExclusionStrategy exlStrategy = new ExclusionStrategy() { 
     @Override 
     public boolean shouldSkipField(FieldAttributes fas) { 
      return (null != fas.getAnnotation(ManyToOne.class)); 
     } 
     @Override 
     public boolean shouldSkipClass(Class<?> classO) { 
      return (null != classO.getAnnotation(ManyToOne.class)); 
     } 
    }; 
    return exlStrategy; 
} 
3

Si está utilizando Jackon para serializar, solo se aplica @JsonBackReference a su mapeo bi-directinal Se va a resolver el problema de referencia circular.

Nota: @JsonBackReference se utiliza para resolver el bucle infinito (StackOverflowError)

+0

'@ JsonIgnore' hizo que mi' JpaRepository' fallara al mapear la propiedad, pero' @ JsonBackReference' resolvió la referencia circular y aún permitió el mapeo adecuado para el atributo problemático – Bramastic

Cuestiones relacionadas