Estoy escribiendo un serializador para serializar POJO a JSON pero atascado en un problema de referencia circular. En la relación uno a muchos bidireccional de hibernación, el padre hace referencia a las referencias secundarias y secundarias al padre y aquí mi serializador muere. (ver código de ejemplo a continuación)
¿Cómo romper este ciclo? ¿Podemos obtener el árbol de propietarios de un objeto para ver si el objeto en sí existe en algún lugar de su propia jerarquía de propietarios? ¿Alguna otra forma de saber si la referencia va a ser circular? o alguna otra idea para resolver este problema?
82
Respuestas:
¿Se puede representar una relación bidireccional en JSON? Algunos formatos de datos no son adecuados para algunos tipos de modelado de datos.
Un método para lidiar con ciclos cuando se trata de atravesar gráficos de objetos es realizar un seguimiento de los objetos que ha visto hasta ahora (usando comparaciones de identidad), para evitar atravesar un ciclo infinito.
fuente
Confío en Google JSON para manejar este tipo de problema utilizando la función
Suponga 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 de GsonBuilder
fuente
Jackson 1.6 (lanzado en septiembre de 2010) tiene soporte específico basado en anotaciones para manejar dicho vínculo padre / hijo, consulte http://wiki.fasterxml.com/JacksonFeatureBiDirReferences . ( Instantánea de Wayback )
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 truco real está en cómo deserializarlo (volver a crear el enlace principal), no solo maneje el lado de la 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 verdaderas ( Wayback Snapshot ), por lo que también puede resolverlo de esta manera.
fuente
Al abordar este problema, tomé el siguiente enfoque (estandarizar el proceso en mi aplicación, hacer que el código sea claro y reutilizable):
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 un valor 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)
o, para excluir el objeto Date
fuente
Si está utilizando Jackon para serializar, simplemente aplique @JsonBackReference a su mapeo bidireccional. Resolverá el problema de referencia circular.
Nota: @JsonBackReference se usa para resolver la recursividad infinita (StackOverflowError)
fuente
@JsonIgnore
hizo miJpaRepository
error al mapear la propiedad, pero@JsonBackReference
resolví la referencia circular y aún permitía el mapeo adecuado para el atributo problemáticoUsé una solución similar a la de Arthur pero en lugar de
setExclusionStrategies
usarGson gson = new GsonBuilder() .excludeFieldsWithoutExposeAnnotation() .create();
y
@Expose
usé la anotación gson para los campos que necesito en el json, otros campos están excluidos.fuente
si está usando Spring Boot, Jackson arroja un error al crear una respuesta a partir de datos circulares / bidireccionales, así que use
ignorar la circularidad
At Parent: @OneToMany(mappedBy="dbApp") @JsonIgnoreProperties("dbApp") private Set<DBQuery> queries; At child: @ManyToOne @JoinColumn(name = "db_app_id") @JsonIgnoreProperties("queries") private DBApp dbApp;
fuente
Si está usando Javascript, hay una solución muy fácil para eso usando el
replacer
parámetro deJSON.stringify()
método 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 tarde, puede recrear fácilmente el objeto real con las referencias cíclicas analizando los datos serializados y modificando la
next
propiedad para que apunte al objeto real si está usando una referencia nombrada con un@
like en este ejemplo.fuente
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; }
fuente
Este error puede aparecer cuando tiene dos objetos:
class object1{ private object2 o2; } class object2{ private object1 o1; }
Con el uso de GSon para la serialización, tengo este error:
Para resolver esto, simplemente agregue la palabra clave transitorio:
class object1{ private object2 o2; } class object2{ transient private object1 o1; }
Como puede ver aquí: ¿Por qué Java tiene campos transitorios?
fuente
Jackson proporciona
JsonIdentityInfo
anotaciones para evitar referencias circulares. Puedes consultar el tutorial aquí .fuente
la respuesta número 8 es mejor, creo que si sabe qué campo está arrojando un error, solo configura el campo 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 que el código sea legible, se mueve un gran comentario del comentario
// **
a continuación.fuente
Por ejemplo, ProductBean tiene serialBean. El mapeo sería una relación bidireccional. Si ahora intentamos usar
gson.toJson()
, terminará con una referencia circular. Para evitar ese problema, puede seguir estos pasos:productBean.serialBean.productBean = null;
gson.toJson();
Eso debería resolver el problema
fuente