¿Cómo resolver referencias circulares en el serializador json causado por el mapeo bidireccional de hibernación?

Estoy escribiendo un serializador para serializar POJO a JSON, pero atascado en el 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?

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

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

Confío en Google JSON para manejar este tipo de problema mediante el uso de la función

Excluir campos de serialización y deserialización

Supongamos una relación bidireccional entre las clases A y B de la siguiente manera

public class A implements Serializable { private B b; } 

Y B

 public class B implements Serializable { private A a; } 

Ahora use GsonBuilder para obtener un objeto Gson personalizado de la siguiente manera ( observe el método setExclusionStrategies )

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

Eche un vistazo a la clase GsonBuilder

Jackson 1.6 (lanzado en septiembre de 2010) tiene soporte específico basado en anotaciones para manejar dicho enlace padre / hijo, ver http://wiki.fasterxml.com/JacksonFeatureBiDirReferences . ( Instantánea de Wayback )

Por supuesto, ya puedes excluir la serialización del enlace principal que ya usa 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 (volver a crear el 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 admite referencias de identidad verdadera ( instantánea de Wayback ), por lo que también puede resolverlo de esta manera.

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. Crea una clase de anotación para usar en campos que te gustaría excluir
  2. Definir una clase que implemente la interfaz ExclusionStrategy de Google
  3. Cree un método simple para generar el objeto GSON utilizando GsonBuilder (similar a la explicación de Arthur)
  4. Anotar los campos para excluir según sea necesario
  5. Aplique las reglas de serialización a su objeto com.google.gson.Gson
  6. Serializar 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, se proporciona nulo al constructor, puede especificar que se excluya otra clase; ambas opciones se agregan 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); 

Si está utilizando Jackon para serializar, simplemente aplique @JsonBackReference a su asignación bidireccional. Resolverá el problema de referencia circular.

Nota: @JsonBackReference se usa para resolver la recursión infinita (StackOverflowError)

Usé una solución similar a la de Arthur, pero en lugar de setExclusionStrategies utilicé

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

y usé @Expose gson annotation para los campos que necesito en el json, otros campos están excluidos.

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

Así es como puedes 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 adelante, puede recrear fácilmente el objeto real con las referencias cíclicas analizando los datos serializados y modificando la propiedad next para apuntar al objeto real si está utilizando una referencia con nombre con un @ como en este ejemplo.

Así es como finalmente lo resolví en mi caso. Esto funciona al menos con Gson y 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; } 

Este error puede aparecer cuando tienes 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, solo agregue palabras clave transitorias:

 class object1{ private object2 o2; } class object2{ transient private object1 o1; } 

Como puede ver aquí: ¿Por qué Java tiene campos transitorios?

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

Jackson proporciona la anotación JsonIdentityInfo para evitar referencias circulares. Puedes consultar el tutorial aquí .

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

 List 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 que el código sea legible, un gran comentario se mueve del comentario // ** a continuación.

java.lang.StackOverflowError [Falló el procesamiento de la solicitud; 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”]

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 del origen de datos.
  2. Iterate la lista y asegúrate de que serialBean no sea nulo, y luego
  3. Establezca productBean.serialBean.productBean = null;
  4. Luego intente usar gson.toJson();

Eso debería resolver el problema