gson.toJson () zgłasza StackOverflowError


87

Chciałbym wygenerować ciąg JSON z mojego obiektu:

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

Za każdym razem, gdy próbuję to zrobić, pojawia się ten błąd:

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)

Oto atrybuty mojej klasy 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;

Atrybuty mojej klasy BomModule, do której się odwołujemy :

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

Masz jakiś pomysł, co powoduje ten błąd? Jak mogę to naprawić?


Może się to zdarzyć, jeśli umieścisz instancję obiektu wewnątrz siebie gdzieś wewnątrz gson.
Christophe Roussy

Wyjątek traci korzenie powodują zaczyna dziennik z JsonWriter.java:473), w jaki sposób zidentyfikować przyczynę tej stackoverflow Gson
Siddharth

Odpowiedzi:


86

Problem polega na tym, że masz odwołanie cykliczne.

W BomModuleklasie, do której się odwołujesz:

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

To odniesienie do siebie BomModule, oczywiście, wcale nie lubiane przez GSON.

Aby obejść ten problem, wystarczy ustawić moduły, nullaby uniknąć cyklicznego zapętlenia. W ten sposób mogę uniknąć wyjątku StackOverFlow-Exception.

item.setModules(null);

Lub zaznacz pola, których nie chcesz wyświetlać w zserializowanym pliku JSON, używając transientsłowa kluczowego, np .:

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

Tak, obiekt BomModule może być częścią innego obiektu BomModule.
nimrod

Ale czy to jest problem? „Kolekcja modułów <BomModule>” to tylko kolekcja i myślę, że gson powinien być w stanie zrobić z niej prostą tablicę?
nimrod

@dooonot: Czy którykolwiek z obiektów w kolekcji odwołuje się do obiektu nadrzędnego?
SLaks

Nie jestem pewien, czy dobrze cię rozumiem, ale tak. Zobacz zaktualizowane pytanie powyżej.
nimrod

@dooonot: Tak jak podejrzewałem, podczas serializacji kolekcji nadrzędnej i podrzędnej przechodzi w nieskończoną pętlę. Jaki rodzaj JSON ma napisać?
SLaks

30

Miałem ten problem, gdy miałem logger Log4J jako właściwość klasy, na przykład:

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

Można to rozwiązać, wykonując rejestrator staticlub po prostu przenosząc go do rzeczywistych funkcji.


4
Absolutnie świetny połów. To samo odniesienie do klasy najwyraźniej wcale nie lubiane przez GSON. Uratował mi wiele bólów głowy! +1
christopher

1
Innym sposobem rozwiązania tego problemu jest dodanie modyfikatora przejściowego do pola
gawi

Rejestrator powinien być przeważnie statyczny. W przeciwnym razie będziesz musiał ponieść koszt uzyskania tej instancji Loggera dla każdego tworzenia obiektu, co prawdopodobnie nie jest tym, czego chcesz. (Koszt nie jest trywialny)
stolsvik

26

Jeśli używasz Realm i otrzymujesz ten błąd, a problem powodujący problem rozszerza RealmObject, nie zapomnij zrobić realm.copyFromRealm(myObject)kopii bez wszystkich powiązań Realm przed przejściem do GSON w celu serializacji.

Brakowało mi zrobienia tego, ponieważ tylko jeden z wielu kopiowanych obiektów ... zajęło mi to wieki, ponieważ ślad stosu nie nazywa klasy / typu obiektu. Chodzi o to, że problem jest spowodowany przez odwołanie cykliczne, ale jest to odwołanie cykliczne gdzieś w klasie bazowej RealmObject, a nie twoja własna podklasa, co utrudnia wykrycie!


1
To jest poprawne! W moim przypadku zmień listę zapytanych obiektów bezpośrednio z dziedziny na ArrayList <Image> copyList = new ArrayList <> (); for (Obraz obraz: obrazy) {copyList.add (realm.copyFromRealm (obraz)); }
Ricardo Mutti

Używając realm, to było dokładnie rozwiązanie, które rozwiązuje problem, dzięki
Jude Fernandes

13

Jak powiedział SLaks, StackOverflowError się zdarza, jeśli masz cykliczne odwołanie w swoim obiekcie.

Aby to naprawić, możesz użyć TypeAdapter dla swojego obiektu.

Na przykład, jeśli potrzebujesz tylko wygenerować String z obiektu, możesz użyć adaptera w następujący sposób:

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

i zarejestruj to w ten sposób:

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

lub w ten sposób, jeśli masz interfejs i chcesz używać adaptera dla wszystkich jego podklas:

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

9

Moja odpowiedź jest trochę spóźniona, ale myślę, że to pytanie nie ma jeszcze dobrego rozwiązania. Znalazłem to pierwotnie tutaj .

Za pomocą Gson możesz oznaczyć pola, które chcesz uwzględnić w json, w @Exposeten sposób:

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

i utwórz obiekt gson z:

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

Okrągłe odniesienia, których po prostu nie ujawniasz. To mi wystarczyło!


Czy wiesz, czy istnieje adnotacja, która jest odwrotna do tego? Jest około 4 pól, które muszę zignorować, a ponad 30 muszę uwzględnić.
jDub9

@ jDub9 Przepraszam za spóźnioną odpowiedź, ale byłem na wakacjach. Spójrz na odpowiedź. Mam nadzieję, że to rozwiązuje twój problem
ffonz

3

Ten błąd występuje często, gdy masz rejestrator w swojej superklasie. Jak zasugerował @Zar wcześniej, możesz użyć statycznego dla swojego pola rejestratora, ale to również działa:

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

PS prawdopodobnie będzie działać iz adnotacją @Expose sprawdź więcej na ten temat tutaj: https://stackoverflow.com/a/7811253/1766166


1

Mam ten sam problem. W moim przypadku powodem było to, że konstruktor mojej serializowanej klasy pobierał zmienną kontekstową, taką jak ta:

public MetaInfo(Context context)

Kiedy usuwam ten argument, błąd zniknął.

public MetaInfo()

1
Napotkałem ten problem podczas przekazywania odwołania do obiektu usługi jako kontekstu. Poprawka polegała na tym, aby zmienna kontekstu stała się statyczna w klasie używającej gson.toJson (this).
user802467

@ user802467 czy masz na myśli usługę androida?
Preetam

1

Edycja: Przepraszam za mój błąd, to moja pierwsza odpowiedź. Dziękuję za rady.

Tworzę własny konwerter Json

Głównym rozwiązaniem, którego użyłem, jest utworzenie zestawu obiektów nadrzędnych dla każdego odniesienia do obiektu. Jeśli odniesienie podrzędne wskazuje na istniejący obiekt nadrzędny, zostanie odrzucone. Następnie łączę się z dodatkowym rozwiązaniem, ograniczając czas odniesienia, aby uniknąć nieskończonej pętli w dwukierunkowej relacji między bytami.

Mój opis nie jest zbyt dobry, mam nadzieję, że wam pomoże.

To mój pierwszy wkład w społeczność Java (rozwiązanie Twojego problemu). Możecie to sprawdzić;) Jest plik README.md https://github.com/trannamtrung1st/TSON


2
Link do rozwiązania jest mile widziany, ale upewnij się, że Twoja odpowiedź jest przydatna bez niego: dodaj kontekst wokół linku, aby inni użytkownicy mieli pojęcie, co to jest i dlaczego się tam znajduje, a następnie zacytuj najbardziej odpowiednią część strony, którą podałeś. ponowne łącze w przypadku, gdy strona docelowa jest niedostępna. Odpowiedzi, które są niewiele więcej niż linkiem, mogą zostać usunięte.
Paul Roub

2
Autopromocja Samo linkowanie do własnej biblioteki lub samouczka nie jest dobrą odpowiedzią. Łączenie się z nim, wyjaśnianie, dlaczego rozwiązuje problem, dostarczanie kodu, jak to zrobić i zrzeczenie się, że napisałeś, zapewnia lepszą odpowiedź. Zobacz: Co oznacza „dobrą” autopromocję?
Shree,

Dzięki wielkie. Zmieniłem odpowiedź. Mam nadzieję, że wszystko będzie dobrze: D
Trần Nam Trung

Podobnie jak powiedzieli inni komentujący, najlepiej jest pokazać w poście najważniejsze części kodu. Nie musisz też przepraszać za błędy w swojej odpowiedzi.
0xCursor

0

W Androidzie przepełnienie stosu gson okazało się deklaracją Handlera. Przeniesiono go do klasy, która nie jest deserializowana.

Opierając się na zaleceniu Zara, uczyniłem handler statycznym, gdy stało się to w innej sekcji kodu. Uruchamianie programu obsługi również działało.


0

BomItemodnosi się do BOMModule( Collection<BomModule> modules) i BOMModuleodnosi się do BOMItem( Collection<BomItem> items). Biblioteka Gson nie lubi odwołań cyklicznych. Usuń tę zależność cykliczną ze swojej klasy. Ja również miałem w przeszłości ten sam problem z gson lib.


0

Miałem ten problem, który wystąpił u mnie, gdy wstawiłem:

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

w moim obiekcie ... co miało sens po mniej więcej godzinie debugowania!



0

Unikaj niepotrzebnych obejść, takich jak ustawianie wartości na null lub zmienianie pól jako przejściowe. Właściwym sposobem na to jest dodanie adnotacji do jednego z pól za pomocą @Expose, a następnie polecenie Gson, aby serializował tylko pola z adnotacją:

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

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

0

Miałem podobny problem, w którym klasa miała zmienną InputStream, której tak naprawdę nie musiałem utrwalać. Dlatego zmiana go na Przejściowa rozwiązała problem.


0

Po pewnym czasie walcząc z tym problemem, wydaje mi się, że mam rozwiązanie. Problem dotyczy nierozwiązanych połączeń dwukierunkowych i sposobu reprezentowania połączeń podczas ich serializacji. Sposobem na naprawienie tego zachowania jest „poinformowanie”, gsonjak serializować obiekty. W tym celu używamyAdapters .

Używając Adapters, możemy powiedzieć, gsonjak serializować każdą właściwość z plikuEntity klasy, a także które właściwości serializować.

Niech Fooi Barbędą dwoma podmiotami, w których Fooma OneToManyzwiązek Bari Barma ManyToOnezwiązek Foo. Definiujemy Baradapter tak, aby podczas gsonserializacji Barzdefiniowanie sposobu serializacji Fooz perspektywy Barcyklicznego odwoływania się nie było możliwe.

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

Tutaj foo_idjest używany do reprezentowania Fooobiektu, który zostałby serializowany i który spowodowałby nasz problem z cyklicznymi odwołaniami. Teraz, gdy używamy adaptera Foonie będzie ponownie serializowany, Bartylko jego identyfikator zostanie pobrany i wstawiony JSON. Teraz mamy Baradapter i możemy go użyć do serializacji Foo. Oto pomysł:

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

Jeszcze jedna rzecz do wyjaśnienia, foo_idnie jest obowiązkowa i można ją pominąć. Celem adaptera w tym przykładzie jest serializacja Bari poprzez umieszczenie foo_idpokazaliśmy, że Barmoże wyzwalać ManyToOnebez powodowania FoowyzwalaniaOneToMany ponownego ...

Odpowiedź opiera się na osobistych doświadczeniach, dlatego zachęcam do komentowania, udowodnienia, że ​​się mylę, poprawienia błędów lub rozszerzenia odpowiedzi. W każdym razie mam nadzieję, że ta odpowiedź komuś się przyda.


Definiowanie adaptera dla samego procesu serializacji uchwytów jest innym sposobem obsługi cyklicznej zależności. Masz na to opcję, chociaż istnieją inne adnotacje, które mogą temu zapobiec, zamiast pisać adaptery.
Sariq Shaikh
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.