gson.toJson () lève StackOverflowError

87

Je souhaite 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 y remédier?

nimrod
la source
Cela peut arriver si vous mettez une instance d'objet à l'intérieur d'elle-même quelque part dans le gson.
Christophe Roussy
L'exception perd la cause racine et démarre le journal avec JsonWriter.java:473), comment identifier la cause racine du stackoverflow Gson
Siddharth

Réponses:

86

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

Dans le cours BomModuleauquel vous faites référence:

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

Cette référence à soi-même BomModulen'est évidemment pas du tout appréciée par GSON.

Une solution de contournement consiste simplement à définir les modules sur nullpour éviter la boucle récursive. 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 transientmot - clé, par exemple:

private transient Collection<BomModule> parentModules;
private transient Collection<BomModule> subModules;
SLaks
la source
Oui, un objet BomModule peut faire partie d'un autre objet BomModule.
nimrod
Mais est-ce un problème? 'Collection <BomModule> modules' n'est qu'une collection, et je pense que gson devrait pouvoir en faire un simple tableau?
nimrod
@dooonot: l'un des objets de la collection fait-il référence à son objet parent?
SLaks
Je ne sais pas si vous avez raison, mais oui. Veuillez consulter la question mise à jour ci-dessus.
nimrod
@dooonot: Comme je le soupçonnais, il entre dans une boucle infinie lors de la sérialisation des collections parent et enfant. Quel genre de JSON attendez-vous qu'il écrive?
SLaks
29

J'ai eu ce problème lorsque j'avais un enregistreur Log4J comme propriété de classe, tel que:

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

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

Zar
la source
4
Absolument super prise. Cette référence personnelle à la classe n'est évidemment pas du tout appréciée par GSON. M'a sauvé beaucoup de maux de tête! +1
christopher
1
une autre façon de le résoudre consiste à ajouter un modificateur transitoire au champ
gawi
l'enregistreur doit être principalement statique. Sinon, vous devrez payer pour obtenir cette instance de Logger pour chaque création d'objet, ce qui n'est probablement pas ce que vous voulez. (Le coût n'est pas anodin)
stolsvik
26

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

J'avais manqué de faire cela pour un seul parmi un tas d'objets copiés ... m'a pris du temps à réaliser 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 la rend plus difficile à repérer!

Breeno
la source
1
C'est correct! Dans mon cas, changez ma liste d'objets interrogés directement du royaume en ArrayList <Image> copyList = new ArrayList <> (); pour (Image image: images) {copyList.add (realm.copyFromRealm (image)); }
Ricardo Mutti
En utilisant le royaume, c'était exactement la solution qui résout le problème, merci
Jude Fernandes
13

Comme SLaks l'a dit, 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 n'avez besoin que de générer une chaîne à partir de votre objet, vous pouvez utiliser un adaptateur comme celui-ci:

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 que vous souhaitez utiliser l'adaptateur pour toutes ses sous-classes:

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

Ma réponse est un peu tardive, 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 ne souhaitez inclure dans JSON avec @Exposecomme celui - ci:

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

et créez l'objet gson avec:

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

Des références circulaires que vous n'exposez tout simplement pas . Cela a fait l'affaire pour moi!

ffonz
la source
Savez-vous s'il existe une annotation qui fait le contraire de cela? Il y a environ 4 champs que je dois ignorer et plus de 30 que je dois inclure.
jDub9
@ jDub9 Désolé pour ma réponse tardive, mais je suis en vacances. Jetez un œil à cette réponse. J'espère que cela résout votre problème
ffonz
3

Cette erreur est courante lorsque vous avez un enregistreur dans votre super classe. Comme @Zar l'a suggéré auparavant, vous pouvez utiliser statique pour votre champ de journalisation, mais cela fonctionne également:

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

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

zygimantus
la source
1

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

public MetaInfo(Context context)

Lorsque je supprime cet argument, l'erreur a disparu.

public MetaInfo()
Denis
la source
1
J'ai rencontré ce problème lors de la transmission de la référence d'objet de service en tant que contexte. Le correctif consistait à rendre la variable de contexte statique dans la classe qui utilise gson.toJson (this).
user802467
@ user802467 voulez-vous dire service Android?
Preetam
1

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

Je crée mon propre convertisseur Json

La principale solution que j'ai utilisée est de 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 avec une solution supplémentaire, en limitant le temps de référence pour éviter une boucle infinitive en relation bidirectionnelle entre entités.

Ma description n'est pas trop bonne, j'espère que cela vous aidera les gars.

C'est ma première contribution à la communauté Java (solution à votre problème). Vous pouvez le vérifier;) Il existe un fichier README.md https://github.com/trannamtrung1st/TSON

Trần Nam Trung
la source
2
Un lien vers une solution est le bienvenu, mais veuillez vous assurer que votre réponse est utile sans elle: ajoutez du contexte autour du lien afin que vos collègues utilisateurs aient une idée de ce que c'est et pourquoi il est là, puis citez la partie la plus pertinente de la page que vous '' relier au cas où la page cible ne serait pas disponible. Les réponses qui ne sont guère plus qu'un lien peuvent être supprimées.
Paul Roub
2
Auto-promotion Le simple fait de créer un lien vers votre propre bibliothèque ou tutoriel n'est pas une bonne réponse. Le lien, en expliquant pourquoi il résout le problème, en fournissant du code sur la façon de le faire et en refusant que vous l'ayez écrit, c'est une meilleure réponse. Voir: Que signifie une «bonne» auto-promotion?
Shree
Merci beaucoup. J'avais édité ma réponse. J'espère que ça ira bien: D
Trần Nam Trung
Semblable à ce que les autres commentateurs ont dit, il est préférable d'afficher les parties les plus importantes de votre code dans votre message. De plus, vous n'avez pas à vous excuser pour les erreurs dans votre réponse.
0xCursor
0

Dans Android, le débordement de pile gson s'est avéré être la déclaration d'un gestionnaire. Je l'ai déplacé vers une classe qui n'est pas désérialisée.

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

Dan
la source
0

BomItemfait référence à BOMModule( Collection<BomModule> modules) et BOMModulefait référence à BOMItem( Collection<BomItem> items). La bibliothèque Gson n'aime pas les références circulaires. Supprimez cette dépendance circulaire de votre classe. J'avais moi aussi été confronté au même problème dans le passé avec gson lib.

Binita Bharati
la source
0

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 environ une heure de débogage!

keesp
la source
0

Pour les utilisateurs d'Android, vous ne pouvez pas sérialiser un en Bundleraison d'une auto-référence à Bundlel'origine d'un StackOverflowError.

Pour sérialiser un bundle, enregistrez un fichierBundleTypeAdapterFactory .

Cordon Rehn
la source
0

Évitez les solutions de contournement inutiles, telles que la définition de valeurs sur null ou la création de champs transitoires. La bonne façon de faire est d'annoter l'un des champs avec @Expose, puis de dire à 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();
Ismael Sarmento
la source
0

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

Kamalakannan V
la source
0

Après un certain temps à lutter contre ce problème, je pense avoir une solution. Le problème concerne les connexions bidirectionnelles non résolues et la façon de représenter les connexions lorsqu'elles sont sérialisées. La manière de corriger ce comportement est de "dire" gsoncomment sérialiser des objets. À cette fin, nous utilisons Adapters.

En utilisant, Adaptersnous pouvons dire gsoncomment sérialiser chaque propriété de votre Entityclasse ainsi que les propriétés à sérialiser.

Soit Fooet Barsoit deux entités où Fooa une OneToManyrelation Baret Bara une ManyToOnerelation avec Foo. Nous définissons l' Baradaptateur de sorte que lors de la gsonsérialisation Bar, en définissant comment sérialiser Foodu point de vue du Barréférencement cyclique ne sera pas possible.

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

Ici foo_idest utilisé pour représenter l' Fooentité qui serait sérialisée et qui causerait notre problème de référencement cyclique. Maintenant, lorsque nous utilisons l'adaptateur, Fooil ne sera plus sérialisé à partir de Barseulement son identifiant sera pris et inséré JSON. Nous avons maintenant un Baradaptateur et nous pouvons l'utiliser pour sérialiser Foo. Voici une idée:

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

Une dernière chose à clarifier, foo_idn'est pas obligatoire et peut être ignorée. Le but de l'adaptateur dans cet exemple est de sérialiser Baret en mettant foo_idnous avons montré qu'il Barpeut se déclencher ManyToOnesans provoquer Foode déclenchement à OneToManynouveau ...

La réponse est basée sur l'expérience personnelle, n'hésitez donc pas à commenter, à me prouver le contraire, à corriger les erreurs ou à élargir la réponse. Quoi qu'il en soit, j'espère que quelqu'un trouvera cette réponse utile.

Quepasa
la source
La définition de l'adaptateur pour le processus de sérialisation de poignée lui-même est une autre façon de gérer la dépendance cyclique. Vous avez l'option pour cela bien qu'il existe d'autres annotations qui peuvent empêcher cela au lieu d'écrire les adaptateurs.
Sariq Shaikh