gson.toJson () löst StackOverflowError aus

85

Ich möchte aus meinem Objekt einen JSON-String generieren:

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

Jedes Mal, wenn ich dies versuche, wird folgende Fehlermeldung angezeigt:

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)

Dies sind die Attribute meiner BomItem- Klasse:

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;

Attribute meiner referenzierten BomModule- Klasse:

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

Irgendeine Idee, was diesen Fehler verursacht? Wie kann ich es reparieren?

Nimrod
quelle
Könnte passieren, wenn Sie eine Objektinstanz irgendwo innerhalb des gson in sich selbst einfügen.
Christophe Roussy
Die Ausnahme verliert die Grundursache und startet das Protokoll mit JsonWriter.java:473), wie man die Grundursache des Gson-Stapelüberlaufs identifiziert
Siddharth

Antworten:

84

Dieses Problem ist, dass Sie einen Zirkelverweis haben.

In der BomModuleKlasse, auf die Sie sich beziehen:

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

Dieser Selbstbezug BomModule, der GSON offensichtlich überhaupt nicht gefällt.

Eine Problemumgehung besteht darin, die Module so einzustellen null, dass die rekursive Schleife vermieden wird. Auf diese Weise kann ich die StackOverFlow-Ausnahme vermeiden.

item.setModules(null);

Oder markieren Sie die Felder, die nicht im serialisierten JSON angezeigt werden sollen, mit dem transientSchlüsselwort, z.

private transient Collection<BomModule> parentModules;
private transient Collection<BomModule> subModules;
SLaks
quelle
Ja, ein BomModule-Objekt kann Teil eines anderen BomModule-Objekts sein.
Nimrod
Aber ist das ein Problem? 'Collection <BomModule> modules' ist nur eine Sammlung, und ich denke, gson sollte in der Lage sein, ein einfaches Array daraus zu machen?
Nimrod
@dooonot: Verweist eines der Objekte in der Sammlung auf das übergeordnete Objekt?
SLaks
Ich bin mir nicht sicher, ob ich dich richtig verstanden habe, aber ja. Bitte beachten Sie die aktualisierte Frage oben.
Nimrod
@dooonot: Wie ich vermutet habe, geht es beim Serialisieren der übergeordneten und untergeordneten Sammlungen in eine Endlosschleife. Welche Art von JSON soll es schreiben?
SLaks
29

Ich hatte dieses Problem, als ich einen Log4J-Logger als Klasseneigenschaft hatte, wie zum Beispiel:

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

Dies kann gelöst werden, indem entweder der Logger erstellt staticoder einfach in die eigentliche (n) Funktion (en) verschoben wird.

Zar
quelle
4
Absolut toller Fang. Dieser Selbstbezug zu der Klasse wird GSON offensichtlich überhaupt nicht gefallen. Hat mir viele Kopfschmerzen erspart! +1
Christopher
1
Eine andere Möglichkeit, dies zu lösen, besteht darin, dem Feld einen vorübergehenden Modifikator hinzuzufügen
gawi
Logger sollte meistens statisch sein. Andernfalls entstehen Ihnen die Kosten für das Abrufen dieser Logger-Instanz für jede Objekterstellung, was wahrscheinlich nicht Ihren Wünschen entspricht. (Die Kosten sind nicht trivial)
Stolsvik
25

Wenn Sie Realm verwenden und dieser Fehler auftritt und das Objekt, das den Fehler verursacht, RealmObject erweitert, vergessen Sie nicht realm.copyFromRealm(myObject), eine Kopie ohne alle Realm-Bindungen zu erstellen, bevor Sie zur Serialisierung an GSON übergeben.

Ich hatte es versäumt, dies nur für eines von vielen Objekten zu tun, die kopiert wurden. Es dauerte ewig, bis mir klar wurde, dass der Stack-Trace die Objektklasse / den Objekttyp nicht benennt. Das Problem wird durch einen Zirkelverweis verursacht, aber es ist ein Zirkelverweis irgendwo in der RealmObject-Basisklasse, nicht Ihre eigene Unterklasse, was das Erkennen erschwert!

Breeno
quelle
1
Das ist richtig! In meinem Fall ändere meine abgefragte Objektliste direkt vom Realm in ArrayList <Image> copyList = new ArrayList <> (); für (Bild Bild: Bilder) {copyList.add (Realm.copyFromRealm (Bild)); }
Ricardo Mutti
Mit Realm war das genau die Lösung, die das Problem löst, danke
Jude Fernandes,
13

Wie SLaks sagte, tritt StackOverflowError auf, wenn Sie einen Zirkelverweis in Ihrem Objekt haben.

Um dies zu beheben, können Sie TypeAdapter für Ihr Objekt verwenden.

Wenn Sie beispielsweise nur einen String aus Ihrem Objekt generieren müssen, können Sie einen Adapter wie den folgenden verwenden:

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

und registrieren Sie es so:

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

oder so, wenn Sie eine Schnittstelle haben und den Adapter für alle seine Unterklassen verwenden möchten:

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

Meine Antwort ist etwas spät, aber ich denke, diese Frage hat noch keine gute Lösung. Ich habe es ursprünglich hier gefunden .

Mit Gson können Sie die Felder markieren Sie tun möchten mit in json aufgenommen werden @Exposewie folgt aus :

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

und erstellen Sie das gson-Objekt mit:

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

Rundschreiben, die Sie einfach nicht aussetzen. Das hat den Trick für mich getan!

ffonz
quelle
Wissen Sie, ob es eine Anmerkung gibt, die das Gegenteil davon bewirkt? Es gibt ungefähr 4 Felder, die ich ignorieren muss, und über 30 muss ich einschließen.
jDub9
@ jDub9 Entschuldigung für meine späte Antwort, aber ich war im Urlaub. Schauen Sie sich diese Antwort an. Hoffe, es löst Ihr Problem
ffonz
3

Dieser Fehler tritt häufig auf, wenn Sie einen Logger in Ihrer Superklasse haben. Wie bereits von @Zar vorgeschlagen, können Sie static für Ihr Logger-Feld verwenden. Dies funktioniert jedoch auch:

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

PS wahrscheinlich wird es funktionieren und mit @Expose Annotation überprüfen Sie mehr dazu hier: https://stackoverflow.com/a/7811253/1766166

Zygimantus
quelle
1

Ich habe das gleiche Problem. In meinem Fall war der Grund, dass der Konstruktor meiner serialisierten Klasse eine Kontextvariable wie folgt verwendet:

public MetaInfo(Context context)

Wenn ich dieses Argument lösche, ist ein Fehler aufgetreten.

public MetaInfo()
Denis
quelle
1
Ich bin auf dieses Problem gestoßen, als ich die Serviceobjektreferenz als Kontext übergeben habe. Korrektur war, die Kontextvariable in der Klasse, die gson.toJson (this) verwendet, statisch zu machen.
user802467
@ user802467 meinst du android service?
Preetam
1

Edit: Sorry für mein schlechtes, das ist meine erste Antwort. Vielen Dank für Ihre Ratschläge.

Ich erstelle meinen eigenen Json Converter

Die Hauptlösung, die ich verwendet habe, besteht darin, für jede Objektreferenz einen übergeordneten Objektsatz zu erstellen. Wenn eine Unterreferenz auf ein vorhandenes übergeordnetes Objekt verweist, wird sie verworfen. Dann kombiniere ich mit einer zusätzlichen Lösung, die die Referenzzeit begrenzt, um eine Endlosschleife in der bidirektionalen Beziehung zwischen Entitäten zu vermeiden.

Meine Beschreibung ist nicht so gut, hoffe es hilft euch.

Dies ist mein erster Beitrag zur Java-Community (Lösung Ihres Problems). Sie können es überprüfen;) Es gibt eine README.md-Datei https://github.com/trannamtrung1st/TSON

Trần Nam Trung
quelle
2
Ein Link zu einer Lösung ist willkommen, aber stellen Sie sicher, dass Ihre Antwort ohne sie nützlich ist: Fügen Sie dem Link einen Kontext hinzu, damit Ihre Mitbenutzer eine Vorstellung davon haben, was es ist und warum es dort ist, und zitieren Sie dann den relevantesten Teil der Seite, die Sie verwenden. erneutes Verknüpfen mit, falls die Zielseite nicht verfügbar ist. Antworten, die kaum mehr als ein Link sind, können gelöscht werden.
Paul Roub
2
Eigenwerbung Nur eine Verknüpfung mit Ihrer eigenen Bibliothek oder Ihrem eigenen Tutorial ist keine gute Antwort. Wenn Sie darauf verlinken, erklären, warum das Problem dadurch gelöst wird, Code dazu bereitstellen und nicht angeben, dass Sie es geschrieben haben, erhalten Sie eine bessere Antwort. Siehe: Was bedeutet „gute“ Eigenwerbung?
Shree
Vielen Dank. Ich hatte meine Antwort bearbeitet. Hoffe es wäre in Ordnung: D
Trần Nam Trung
Ähnlich wie bei den anderen Kommentatoren wird bevorzugt, dass Sie die wichtigsten Teile Ihres Codes in Ihrem Beitrag anzeigen. Außerdem müssen Sie sich nicht für Fehler in Ihrer Antwort entschuldigen.
0xCursor
0

In Android stellte sich heraus, dass der Gson-Stack-Überlauf die Deklaration eines Handlers war. Verschob es in eine Klasse, die nicht deserialisiert wird.

Auf Empfehlung von Zar habe ich den Handler statisch gemacht, als dies in einem anderen Codeabschnitt geschah. Es hat auch funktioniert, den Handler statisch zu machen.

Dan
quelle
0

BomItemverweist auf BOMModule( Collection<BomModule> modules) und BOMModuleverweist auf BOMItem( Collection<BomItem> items). Die Gson-Bibliothek mag keine Zirkelverweise. Entfernen Sie diese zirkuläre Abhängigkeit aus Ihrer Klasse. Auch ich hatte in der Vergangenheit mit gson lib das gleiche Problem.

Binita Bharati
quelle
0

Ich hatte dieses Problem bei mir, als ich sagte:

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

in meinem Objekt ... was nach ungefähr einer Stunde Debuggen vollkommen Sinn machte!

keesp
quelle
0

Für Android-Benutzer können Sie a Bundleaufgrund einer Selbstreferenz zur BundleVerursachung von a nicht serialisieren StackOverflowError.

Um ein Bundle zu serialisieren, registrieren Sie aBundleTypeAdapterFactory .

Cord Rehn
quelle
0

Vermeiden Sie unnötige Problemumgehungen, z. B. das Setzen von Werten auf Null oder das Übertragen von Feldern. Der richtige Weg, dies zu tun, besteht darin, eines der Felder mit @Expose zu kommentieren und dann Gson anzuweisen, nur die Felder mit der Annotation zu serialisieren:

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

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

Ich hatte ein ähnliches Problem, bei dem die Klasse eine InputStream-Variable hatte, die ich nicht wirklich beibehalten musste. Daher wurde das Problem durch Ändern in Transient behoben.

Kamalakannan V.
quelle
0

Nach einiger Zeit des Kampfes mit diesem Problem glaube ich, dass ich eine Lösung habe. Das Problem liegt in ungelösten bidirektionalen Verbindungen und in der Darstellung von Verbindungen, wenn diese serialisiert werden. Die Möglichkeit, dieses Verhalten zu beheben, besteht darin, zu "erklären", gsonwie Objekte serialisiert werden sollen. Zu diesem Zweck verwenden wir Adapters.

Mithilfe von können Adapterswir feststellen, gsonwie jede Eigenschaft aus Ihrer EntityKlasse serialisiert wird und welche Eigenschaften serialisiert werden sollen.

Lassen Sie Foound Barzwei Einheiten, in denen Foohat OneToManygegenüber Barund Barhat ManyToOnegegenüber Foo. Wir definieren den BarAdapter so, dass beim gsonSerialisieren keine BarSerialisierung Fooaus Sicht der Barzyklischen Referenzierung möglich ist.

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

Hier foo_idwird eine FooEntität dargestellt , die serialisiert werden würde und die unser zyklisches Referenzierungsproblem verursachen würde. Wenn wir jetzt den Adapter verwenden, Foowird er nicht erneut serialisiert, sondern Barnur seine ID wird übernommen und eingegeben JSON. Jetzt haben wir einen BarAdapter und können ihn zum Serialisieren verwenden Foo. Hier ist die Idee:

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

Eine weitere Sache zu klären, foo_idist nicht obligatorisch und kann übersprungen werden. Der Zweck des Adapters in diesem Beispiel ist die Serialisierung. BarDurch das Setzen haben foo_idwir gezeigt, dass Bardas auslösen kann, ManyToOneohne dass Fooes OneToManyerneut ausgelöst wird ...

Die Antwort basiert auf persönlicher Erfahrung. Sie können daher gerne Kommentare abgeben, mir das Gegenteil beweisen, Fehler beheben oder die Antwort erweitern. Trotzdem hoffe ich, dass jemand diese Antwort nützlich findet.

quepasa
quelle
Das Definieren des Adapters für den Handle-Serialisierungsprozess selbst ist eine weitere Möglichkeit, die zyklische Abhängigkeit zu behandeln. Sie haben die Möglichkeit dazu, obwohl es andere Anmerkungen gibt, die dies verhindern können, anstatt die Adapter zu schreiben.
Sariq Shaikh