web-dev-qa-db-fra.com

gson.toJson () émet StackOverflowError

J'aimerais générer une chaîne JSON à partir de mon objet:

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

Chaque fois que j'essaie de faire cela, j'obtiens cette erreur:

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)

Voici les attributs de ma 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;

Attributs de ma classe BomModule référencée:

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

Une idée de ce qui cause cette erreur? Comment puis-je le réparer?

64
nimrod

Ce problème est que vous avez une référence circulaire.

Dans la classe BomModule, vous faites référence à:

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

Cette référence à BomModule n'est évidemment pas appréciée du tout par GSON.

Une solution de contournement consiste simplement à définir les modules sur null pour éviter les boucles récursives. De cette façon, je peux éviter l’exception StackOverFlow.

item.setModules(null);

Ou marquez les champs que vous ne voulez pas à afficher dans le JSON sérialisé en utilisant le mot clé transient, par exemple:

private transient Collection<BomModule> parentModules;
private transient Collection<BomModule> subModules;
62
SLaks

J'ai eu ce problème quand j'avais un logger Log4J en tant que propriété de classe, tel que:

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

Cela peut être résolu en rendant l'enregistreur static ou simplement en le déplaçant dans la ou les fonctions réelles.

24
Zar

Si vous utilisez Realm et que vous obtenez cette erreur et que l'objet qui vous pose problème étend RealmObject, n'oubliez pas de faire realm.copyFromRealm(myObject) pour créer une copie sans toutes les liaisons Realm avant de passer à GSON pour la sérialisation.

J'avais raté cela pour seulement un objet parmi un groupe d'objets en cours de copie ... cela m'a pris du temps à comprendre, car la trace de la pile ne nomme pas la classe/le type d'objet. Le problème est que le problème est causé par une référence circulaire, mais c'est une référence circulaire quelque part dans la classe de base RealmObject, pas votre propre sous-classe, ce qui le rend plus difficile à repérer!

15
Breeno

Comme le dit SLaks, StackOverflowError se produit si vous avez une référence circulaire dans votre objet.

Pour résoudre ce problème, vous pouvez utiliser TypeAdapter pour votre objet.

Par exemple, si vous ne devez générer que String à partir de votre objet, vous pouvez utiliser un adaptateur comme suit:

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

et enregistrez-le comme ceci:

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

ou comme ceci, si vous avez une interface et souhaitez utiliser l'adaptateur pour toutes ses sous-classes:

Gson gson = new GsonBuilder()
               .registerTypeHierarchyAdapter(BomItemInterface.class, new MyTypeAdapter<BomItemInterface>())
               .create();
10
borislubimov

Ma réponse est un peu en retard, mais je pense que cette question n'a pas encore de bonne solution. Je l'ai trouvé à l'origine ici .

Avec Gson, vous pouvez marquer les champs que vous do souhaitez inclure dans json avec @Expose comme ceci:

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

et créez l'objet gson avec:

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

Les références circulaires que vous venez ne pas exposer. Cela a fait le tour pour moi!

5
ffonz

Cette erreur est courante lorsque vous avez un enregistreur dans votre super classe. Comme @Zar l'avait suggéré précédemment, vous pouvez utiliser static pour votre champ enregistreur, mais cela fonctionne également:

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

P.S. cela fonctionnera probablement et avec @Expose annotation, vérifiez plus à ce sujet ici: https://stackoverflow.com/a/7811253/1766166

2
zygimantus

Edit: Désolé pour mon mauvais, c'est ma première réponse. Merci pour vos conseils.

Je crée mon propre convertisseur Json

La solution principale que j'ai utilisée consiste à créer un ensemble d'objets parents pour chaque référence d'objet. Si une sous-référence pointe vers un objet parent existant, elle sera supprimée. Ensuite, je combine une solution supplémentaire en limitant le temps de référence pour éviter une boucle infinitive dans les relations bidirectionnelles entre entités.

Ma description n'est pas très bonne, j'espère que cela vous aidera.

Ceci est ma première contribution à la communauté Java (solution à votre problème). Vous pouvez y accéder;) Il existe un fichier README.md https://github.com/trannamtrung1st/TSON

1
Trần Nam Trung

J'ai le même problème. Dans mon cas, la raison était que le constructeur de ma classe sérialisée prenait une variable de contexte, comme ceci:

public MetaInfo(Context context)

Lorsque je supprime cet argument, l'erreur est partie.

public MetaInfo()
1
Denis

Évitez les solutions de contournement inutiles, comme définir des valeurs sur null ou rendre les champs transitoires. La bonne façon de faire est d’annoter l’un des champs avec @Expose puis d’indiquer à Gson de ne sérialiser que les champs avec l’annotation:

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

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

Pour les utilisateurs d'Android, vous ne pouvez pas sérialiser une Bundle en raison d'une auto-référence à Bundle provoquant une StackOverflowError.

Pour sérialiser un paquet, enregistrez une BundleTypeAdapterFactory .

0
Cord Rehn

BomItem se réfère à BOMModule (Collection<BomModule> modules) et BOMModule se réfère à BOMItem (Collection<BomItem> items) La bibliothèque Gson n'aime pas les références circulaires. Supprimez cette dépendance circulaire de votre classe. Moi aussi, j'avais déjà rencontré le même problème avec Gson Lib.

0
Binita Bharati

J'ai eu un problème similaire où la classe avait une variable InputStream que je n'avais pas vraiment besoin de persister. Par conséquent, le changer en Transitoire a résolu le problème.

0
Kamalakannan V

Sous Android, le débordement de pile gson s'est avéré être la déclaration d'un gestionnaire. Déplacé dans une classe qui n'est pas en cours de désérialisation.

Sur la recommandation de Zar, j'ai rendu le gestionnaire statique lorsque cela se produisait dans une autre section de code. Rendre le gestionnaire statique a également fonctionné.

0
Dan

J'ai eu ce problème pour moi quand j'ai mis:

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

dans mon objet ... ce qui était parfaitement logique après une heure ou deux de débogage!

0
keesp