Como resolver referência circular no serializador json causada pelo mapeamento bidirecional de hibernação?

Eu estou escrevendo um serializador para serializar POJO para JSON, mas preso em problema de referência circular. Na relação um-para-muitos bidirecional de hibernação, o pai referencia referências filho e filho de volta ao pai e aqui meu serializador morre. (veja o código de exemplo abaixo)
Como quebrar esse ciclo? Podemos obter a tree proprietária de um object para ver se o próprio object existe em algum lugar em sua própria hierarquia de proprietários? Qualquer outra maneira de descobrir se a referência será circular? ou alguma outra ideia para resolver este problema?

    Um relacionamento bidirecional pode ser representado em JSON? Alguns formatos de dados não são adequados para alguns tipos de modelagem de dados.

    Um método para lidar com ciclos ao lidar com charts de object de travessia é manter o controle de quais objects você viu até agora (usando comparações de identidade), para evitar que você percorra um ciclo infinito.

    Eu confio no Google JSON para lidar com esse tipo de problema usando o recurso

    Excluindo campos de serialização e desserialização

    Suponha uma relação bidirecional entre as classs A e B da seguinte forma

    public class A implements Serializable { private B b; } 

    E B

     public class B implements Serializable { private A a; } 

    Agora use GsonBuilder Para obter um object personalizado do Gson da seguinte maneira (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(); 

    Agora nossa referência circular

     A a = new A(); B b = new B(); a.setB(b); b.setA(a); String json = gson.toJson(a); System.out.println(json); 

    Dê uma olhada na class GsonBuilder

    O Jackson 1.6 (lançado em setembro de 2010) possui suporte específico baseado em annotations para lidar com essa vinculação pai / filho, consulte http://wiki.fasterxml.com/JacksonFeatureBiDirReferences . ( Instantâneo de Wayback )

    É claro que você já pode excluir a serialização do link pai já usando a maioria dos pacotes de processamento JSON (jackson, gson e flex-json pelo menos suportam isso), mas o verdadeiro truque está em como desserializá-lo (recriar link pai), não apenas manipular o lado de serialização. Embora sons como por exemplo apenas exclusão possam funcionar para você.

    EDIT (Abril de 2012): O Jackson 2.0 agora suporta referências de identidade verdadeira ( Wayback Snapshot ), para que você possa resolvê-lo dessa maneira também.

    Ao abordar esse problema, tomei a seguinte abordagem (padronizando o processo em meu aplicativo, tornando o código claro e reutilizável):

    1. Crie uma class de anotação para ser usada nos campos que você deseja excluir
    2. Defina uma class que implemente a interface ExclusionStrategy do Google
    3. Crie um método simples para gerar o object GSON usando o GsonBuilder (semelhante à explicação de Arthur)
    4. Anote os campos a serem excluídos conforme necessário
    5. Aplique as regras de serialização ao seu object com.google.gson.Gson
    6. Serialize seu object

    Aqui está o 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)

    No primeiro caso, null é fornecido ao construtor, você pode especificar outra class a ser excluída – ambas as opções são adicionadas abaixo

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

    6)

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

    ou, para excluir o object Date

     String jsonRepresentation = _gsonObj.toJson(_myobject); 

    Se você estiver usando Jackon para serializar, basta aplicar @JsonBackReference ao seu mapeamento bidirecional. Ele resolverá o problema de referência circular.

    Nota: @JsonBackReference é usado para resolver a recursion Infinite (StackOverflowError)

    setExclusionStrategies uma solução semelhante à de Arthur, mas em vez de setExclusionStrategies eu usei

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

    e usei a anotação @Expose @Expose para campos que eu preciso no json, outros campos são excluídos.

    Se você estiver usando JavaScript, há uma solução muito fácil para isso usando o parâmetro replacer do método JSON.stringify() , onde você pode passar uma function para modificar o comportamento de serialização padrão.

    Veja como você pode usá-lo. Considere o exemplo abaixo com 4 nós em um 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); } 

    Posteriormente, você pode recriar com facilidade o object real com as referências cíclicas analisando os dados serializados e modificando a next propriedade para apontar para o object real se estiver usando uma referência nomeada com @ como neste exemplo.

    É assim que eu finalmente resolvi no meu caso. Isso funciona pelo menos com 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; } 

    Este erro pode aparecer quando você tem dois objects:

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

    Com o uso do GSon para serialização, recebi este erro:

     java.lang.IllegalStateException: circular reference error Offending field: o1 

    Para resolver isso, basta adicionar o transiente da palavra-chave:

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

    Como você pode ver aqui: Por que Java tem campos transitórios?

    A palavra-chave temporária em Java é usada para indicar que um campo não deve ser serializado.

    Jackson fornece a anotação JsonIdentityInfo para evitar referências circulares. Você pode verificar o tutorial aqui .

    a resposta número 8 é a melhor, acho que se você souber que campo está jogando um erro, você apenas ajusta o fild em null e resolve.

     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 tornar o código legível, um comentário grande é movido do comentário // ** para abaixo.

    java.lang.StackOverflowError [Request processing failed; exceção aninhada é org.springframework.http.converter.HttpMessageNotWritableException: Não foi possível escrever JSON: Recursão Infinita (StackOverflowError) (através da cadeia de referência: com.service.pegazo.bo.RequestMessageProfessional [“requestMessage”] -> com.service.pegazo. bo.RequestMessage [“requestMessageProfessionals”]

    Por exemplo, o ProductBean tem o serialBean. O mapeamento seria relacionamento bidirecional. Se agora tentarmos usar o gson.toJson() , ele terá uma referência circular. Para evitar esse problema, você pode seguir estas etapas:

    1. Recupere os resultados da fonte de dados.
    2. Iterar a lista e garantir que o serialBean não seja nulo e, em seguida,
    3. Defina productBean.serialBean.productBean = null;
    4. Em seguida, tente usar gson.toJson();

    Isso deve resolver o problema