gson.toJson () lança StackOverflowError

87

Eu gostaria de gerar uma string JSON do meu objeto:

Gson gson = new Gson();
String json = gson.toJson(item);

Sempre que tento fazer isso, recebo este erro:

14:46:40,236 ERROR [[BomItemToJSON]] Servlet.service() for servlet BomItemToJSON threw exception
java.lang.StackOverflowError
    at com.google.gson.stream.JsonWriter.string(JsonWriter.java:473)
    at com.google.gson.stream.JsonWriter.writeDeferredName(JsonWriter.java:347)
    at com.google.gson.stream.JsonWriter.value(JsonWriter.java:440)
    at com.google.gson.internal.bind.TypeAdapters$7.write(TypeAdapters.java:235)
    at com.google.gson.internal.bind.TypeAdapters$7.write(TypeAdapters.java:220)
    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:200)
    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:96)
    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:60)
    at com.google.gson.Gson$FutureTypeAdapter.write(Gson.java:843)

Estes são os atributos da minha classe BomItem :

private int itemId;
private Collection<BomModule> modules;
private boolean deprecated;
private String partNumber;
private String description; //LOB
private int quantity;
private String unitPriceDollar;
private String unitPriceEuro;
private String discount; 
private String totalDollar;
private String totalEuro;
private String itemClass;
private String itemType;
private String vendor;
private Calendar listPriceDate;
private String unitWeight;
private String unitAveragePower;
private String unitMaxHeatDissipation;
private String unitRackSpace;

Atributos da minha classe BomModule referenciada :

private int moduleId;
private String moduleName;
private boolean isRootModule;
private Collection<BomModule> parentModules;
private Collection<BomModule> subModules;
private Collection<BomItem> items;
private int quantity;

Alguma ideia do que causa esse erro? Como posso corrigir isso?

Nimrod
fonte
Pode acontecer se você colocar uma instância de objeto dentro dela mesma em algum lugar dentro do gson.
Christophe Roussy
A exceção perde a causa raiz e inicia o log com JsonWriter.java:473), como identificar a causa raiz do stackoverflow Gson
Siddharth

Respostas:

86

Esse problema é que você tem uma referência circular.

Na BomModuleaula a que você está se referindo:

private Collection<BomModule> parentModules;
private Collection<BomModule> subModules;

Essa auto-referência a BomModule , obviamente, não é apreciada por GSON.

Uma solução alternativa é apenas definir os módulos nullpara evitar o loop recursivo. Dessa forma, posso evitar a exceção StackOverFlow.

item.setModules(null);

Ou marque os campos que não deseja que apareçam no json serializado usando a transientpalavra - chave, por exemplo:

private transient Collection<BomModule> parentModules;
private transient Collection<BomModule> subModules;
SLaks
fonte
Sim, um objeto BomModule pode fazer parte de outro objeto BomModule.
nimrod
Mas isso é um problema? 'Módulos de <BomModule> de coleção' é apenas uma coleção, e eu acho que gson deve ser capaz de fazer um array simples com isso?
nimrod
@dooonot: Algum dos objetos na coleção faz referência a seu objeto pai?
SLaks de
Não tenho certeza se entendi bem, mas sim. Por favor, veja a pergunta atualizada acima.
nimrod
@dooonot: Como eu suspeitava, ele entra em um loop infinito ao serializar as coleções pai e filho. Que tipo de JSON você espera que ele escreva?
SLaks de
29

Tive esse problema quando tinha um registrador Log4J como uma propriedade de classe, como:

private Logger logger = Logger.getLogger(Foo.class);

Isso pode ser resolvido criando o logger staticou simplesmente movendo-o para as funções reais.

Zar
fonte
4
Absolutamente grande captura. Essa auto-referência à classe obviamente não é apreciada por GSON. Me salvou de muitas dores de cabeça! +1
christopher
1
outra maneira de resolver isso é adicionando um modificador de transiente ao campo
gawi
logger deve ser principalmente estático. Caso contrário, você incorrerá no custo de obter essa instância do Logger para cada criação de objeto, o que provavelmente não é o que você deseja. (O custo não é trivial)
stolsvik
26

Se você estiver usando o Realm e obtiver este erro, e o objeto que está causando o problema estende o RealmObject, não se esqueça realm.copyFromRealm(myObject)de criar uma cópia sem todas as ligações do Realm antes de passar para o GSON para serialização.

Eu senti falta de fazer isso por apenas um entre um monte de objetos sendo copiados ... levei muito tempo para perceber, pois o rastreamento de pilha não nomeia a classe / tipo de objeto. O problema é que o problema é causado por uma referência circular, mas é uma referência circular em algum lugar da classe base RealmObject, não sua própria subclasse, o que torna mais difícil identificá-la!

Breeno
fonte
1
Está correto! No meu caso, altere minha lista de objetos consultada diretamente do reino para ArrayList <Image> copyList = new ArrayList <> (); para (imagem de imagem: imagens) {copyList.add (realm.copyFromRealm (imagem)); }
Ricardo Mutti
Usando realm, essa foi exatamente a solução que resolve o problema, obrigado
Jude Fernandes
13

Como SLaks disse StackOverflowError acontecerá se você tiver uma referência circular em seu objeto.

Para corrigir isso, você pode usar TypeAdapter para o seu objeto.

Por exemplo, se você precisa apenas gerar String a partir do seu objeto, você pode usar o adaptador como este:

class MyTypeAdapter<T> extends TypeAdapter<T> {
    public T read(JsonReader reader) throws IOException {
        return null;
    }

    public void write(JsonWriter writer, T obj) throws IOException {
        if (obj == null) {
            writer.nullValue();
            return;
        }
        writer.value(obj.toString());
    }
}

e registre-o assim:

Gson gson = new GsonBuilder()
               .registerTypeAdapter(BomItem.class, new MyTypeAdapter<BomItem>())
               .create();

ou assim, se você tem interface e deseja usar o adaptador para todas as suas subclasses:

Gson gson = new GsonBuilder()
               .registerTypeHierarchyAdapter(BomItemInterface.class, new MyTypeAdapter<BomItemInterface>())
               .create();
Borislubimov
fonte
9

Minha resposta está um pouco atrasada, mas acho que essa questão ainda não tem uma boa solução. Eu o encontrei originalmente aqui .

Com Gson você pode marcar os campos que não querem ser incluídos em JSON com @Exposecomo esta:

@Expose
String myString;  // will be serialized as myString

e crie o objeto gson com:

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

Referências circulares que você simplesmente não expõe. Isso funcionou para mim!

ffonz
fonte
Você sabe se existe uma anotação que faz o oposto disso? Existem cerca de 4 campos que preciso ignorar e mais de 30 que preciso incluir.
jDub9
@ jDub9 Desculpe pelo atraso na resposta, mas estou de férias. Dê uma olhada nesta resposta. Espero que isso resolva seu problema
ffonz
3

Este erro é comum quando você tem um logger em sua superclasse. Como @Zar sugeriu antes, você pode usar estático para o campo do registrador, mas isso também funciona:

protected final transient Logger logger = Logger.getLogger(this.getClass());

PS provavelmente funcionará e com a anotação @Expose verifique mais sobre isso aqui: https://stackoverflow.com/a/7811253/1766166

zygimantus
fonte
1

Eu tenho o mesmo problema. No meu caso, o motivo foi que o construtor da minha classe serializada leva variável de contexto, como esta:

public MetaInfo(Context context)

Quando excluo esse argumento, o erro desaparece.

public MetaInfo()
Denis
fonte
1
Eu encontrei esse problema ao passar a referência do objeto de serviço como contexto. A correção era tornar a variável de contexto estática na classe que usa gson.toJson (this).
user802467
@ user802467 quer dizer serviço android?
Preetam de
1

Edit: Desculpe pelo meu erro, esta é minha primeira resposta. Obrigado por seus conselhos.

Eu crio meu próprio conversor Json

A principal solução que usei foi criar um conjunto de objetos pais para cada referência de objeto. Se uma sub-referência apontar para um objeto pai existente, ela será descartada. Então eu combino com uma solução extra, limitando o tempo de referência para evitar loop infinitivo no relacionamento bidirecional entre entidades.

Minha descrição não é muito boa, espero que ajude vocês.

Esta é minha primeira contribuição para a comunidade Java (solução para seu problema). Você pode verificar;) Há um arquivo README.md https://github.com/trannamtrung1st/TSON

Trần Nam Trung
fonte
2
Um link para uma solução é bem-vindo, mas certifique-se de que sua resposta seja útil sem ele: adicione contexto ao link para que seus outros usuários tenham uma ideia do que ele é e por que está lá, depois cite a parte mais relevante da página que você ' novo link para caso a página de destino não esteja disponível. Respostas que são pouco mais do que um link podem ser excluídas.
Paul Roub
2
Auto-promoção Apenas criar um link para sua própria biblioteca ou tutorial não é uma boa resposta. Vincular a ele, explicar por que ele resolve o problema, fornecer o código sobre como fazer isso e negar que você o escreveu é uma resposta melhor. Veja: O que significa “boa” autopromoção?
Shree
Muito obrigado. Eu editei minha resposta. Espero que esteja tudo bem: D
Trần Nam Trung
Semelhante ao que os outros comentadores disseram, é preferível que você mostre as partes mais importantes do seu código em sua postagem. Além disso, você não precisa se desculpar por erros em sua resposta.
0xCursor
0

No Android, gson stack overflow acabou sendo a declaração de um Handler. Movido para uma classe que não está sendo desserializada.

Com base na recomendação de Zar, tornei o manipulador estático quando isso aconteceu em outra seção do código. Deixar o manipulador estático também funcionou.

Dan
fonte
0

BomItemrefere-se a BOMModule( Collection<BomModule> modules) e BOMModulerefere-se a BOMItem( Collection<BomItem> items). A biblioteca Gson não gosta de referências circulares. Remova esta dependência circular de sua classe. Eu também havia enfrentado o mesmo problema no passado com o gson lib.

Binita Bharati
fonte
0

Eu tive esse problema ocorrendo para mim quando coloquei:

Logger logger = Logger.getLogger( this.getClass().getName() );

no meu objeto ... o que fazia todo o sentido depois de uma hora ou mais de depuração!

Keesp
fonte
0

Para usuários do Android, você não pode serializar um Bundledevido a uma auto-referência para Bundlecausar um StackOverflowError.

Para serializar um pacote, registre aBundleTypeAdapterFactory .

Cord Rehn
fonte
0

Evite soluções alternativas desnecessárias, como definir valores como nulos ou tornar os campos temporários. A maneira certa de fazer isso é anotar um dos campos com @Expose e, em seguida, dizer ao Gson para serializar apenas os campos com a anotação:

private Collection<BomModule> parentModules;
@Expose
private Collection<BomModule> subModules;

...
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
Ismael Sarmento
fonte
0

Tive um problema semelhante em que a classe tinha uma variável InputStream que realmente não precisava persistir. Portanto, alterá-lo para Transiente resolveu o problema.

Kamalakannan V
fonte
0

Depois de algum tempo lutando com esse problema, acredito ter uma solução. O problema está nas conexões bidirecionais não resolvidas e em como representar as conexões quando elas estão sendo serializadas. A maneira de corrigir esse comportamento é "dizer" gsoncomo serializar objetos. Para esse efeito, usamos Adapters.

Ao usar Adapters, podemos dizer gsoncomo serializar cada propriedade de sua Entityclasse, bem como quais propriedades serializar.

Deixe Fooe Barseja duas entidades onde Footem OneToManyrelação com Bare Bartem ManyToOnerelação com Foo. Definimos Baradaptador de forma que, ao gsonserializar Bar, definir como serializar Fooda perspectiva da Barreferência cíclica não será possível.

public class BarAdapter implements JsonSerializer<Bar> {
    @Override
    public JsonElement serialize(Bar bar, Type typeOfSrc, JsonSerializationContext context) {
        JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("id", bar.getId());
        jsonObject.addProperty("name", bar.getName());
        jsonObject.addProperty("foo_id", bar.getFoo().getId());
        return jsonObject;
    }
}

Aqui foo_idé usado para representar a Fooentidade que seria serializada e que causaria nosso problema de referência cíclica. Agora, quando usarmos o adaptador Foo, não será serializado novamente, Barapenas seu id será obtido e inserido JSON. Agora temos Baradaptador e podemos usá-lo para serializar Foo. Aqui está a ideia:

public String getSomething() {
    //getRelevantFoos() is some method that fetches foos from database, and puts them in list
    List<Foo> fooList = getRelevantFoos();

    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(Bar.class, new BarAdapter());
    Gson gson = gsonBuilder.create();

    String jsonResponse = gson.toJson(fooList);
    return jsonResponse;
}

Mais uma coisa a esclarecer, foo_idnão é obrigatório e pode ser ignorado. O objetivo do adaptador neste exemplo é serializar Bare, colocando foo_id, mostramos que Barpode disparar ManyToOnesem causar o FoodisparoOneToMany novamente ...

A resposta é baseada na experiência pessoal, portanto, fique à vontade para comentar, provar que estou errado, consertar erros ou expandir a resposta. De qualquer forma, espero que alguém ache esta resposta útil.

Quepasa
fonte
Definir o adaptador para o processo de serialização do identificador em si é outra maneira de lidar com a dependência cíclica. Você tem a opção de fazê-lo, embora existam outras anotações que podem evitar que isso aconteça em vez de gravar os adaptadores.
Sariq Shaikh