gson.toJson () melempar StackOverflowError

87

Saya ingin membuat String JSON dari objek saya:

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

Setiap kali saya mencoba melakukan ini, saya mendapatkan kesalahan ini:

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)

Ini adalah atribut kelas BomItem saya :

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;

Atribut kelas BomModule yang saya rujuk :

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

Tahu apa yang menyebabkan kesalahan ini? Bagaimana cara memperbaikinya?

nimrod
sumber
Bisa terjadi jika Anda meletakkan sebuah instance objek di dalam dirinya sendiri di suatu tempat di dalam gson.
Christophe Roussy
Pengecualian kehilangan akar penyebab dan memulai log dengan JsonWriter.java:473), bagaimana mengidentifikasi akar penyebab Gson stackoverflow
Siddharth

Jawaban:

86

Masalahnya adalah Anda memiliki referensi melingkar.

Di BomModulekelas yang Anda rujuk ke:

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

Referensi diri itu BomModule, jelas, sama sekali tidak disukai oleh GSON.

Solusinya hanya mengatur modul nulluntuk menghindari perulangan rekursif. Dengan cara ini saya dapat menghindari StackOverFlow-Exception.

item.setModules(null);

Atau tandai kolom yang tidak ingin Anda tampilkan di serialized json dengan menggunakan transientkata kunci, misalnya:

private transient Collection<BomModule> parentModules;
private transient Collection<BomModule> subModules;
SLaks
sumber
Ya, objek BomModule dapat menjadi bagian dari objek BomModule lainnya.
nimrod
Tapi apakah itu masalah? 'Collection <BomModule> modules' hanyalah sebuah koleksi, dan menurut saya gson harus dapat membuat array sederhana darinya?
nimrod
@dooonot: Apakah salah satu objek dalam koleksi mereferensikan objek induknya?
SLaks
Saya tidak yakin apakah saya membuat Anda benar, tapi ya. Silakan lihat pertanyaan terbaru di atas.
nimrod
@dooonot: Seperti yang saya duga, ini masuk ke loop tak terbatas saat membuat serialisasi koleksi induk dan anak. JSON macam apa yang Anda harapkan untuk ditulisnya?
SLaks
30

Saya mengalami masalah ini ketika saya memiliki logger Log4J sebagai properti kelas, seperti:

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

Ini dapat diselesaikan dengan membuat logger staticatau hanya dengan memindahkannya ke fungsi sebenarnya.

Zar
sumber
4
Tangkapan yang benar-benar bagus. Referensi diri ke kelas itu jelas tidak disukai oleh GSON sama sekali. Menyelamatkan saya dari banyak sakit kepala! +1
Christopher
1
cara lain untuk mengatasinya, adalah dengan menambahkan pengubah sementara ke lapangan
gawi
Logger sebagian besar harus statis. Jika tidak, Anda akan dikenakan biaya untuk mendapatkan instance Logger tersebut untuk setiap pembuatan objek, yang mungkin bukan yang Anda inginkan. (Biayanya tidak sepele)
stolsvik
26

Jika Anda menggunakan Realm dan mendapatkan kesalahan ini, dan objek yang memberikan masalah tersebut memperluas RealmObject, jangan lupa lakukan realm.copyFromRealm(myObject) untuk membuat salinan tanpa semua binding Realm sebelum meneruskan ke GSON untuk serialisasi.

Saya melewatkan melakukan ini hanya untuk satu di antara sekumpulan objek yang sedang disalin ... butuh waktu lama untuk saya sadari karena jejak tumpukan tidak menyebutkan kelas / jenis objek. Masalahnya, masalahnya disebabkan oleh referensi melingkar, tetapi ini adalah referensi melingkar di suatu tempat di kelas dasar RealmObject, bukan subkelas Anda sendiri, yang membuatnya lebih sulit dikenali!

Breeno
sumber
1
Itu benar! Dalam kasus saya, ubah daftar objek saya ditanyai langsung dari ranah ke ArrayList <Image> copyList = new ArrayList <> (); untuk (Image image: images) {copyList.add (realm.copyFromRealm (image)); }
Ricardo Mutti
Menggunakan alam, itulah solusi yang memecahkan masalah, terima kasih
Jude Fernandes
13

Seperti kata SLaks, StackOverflowError terjadi jika Anda memiliki referensi melingkar di objek Anda.

Untuk memperbaikinya Anda bisa menggunakan TypeAdapter untuk objek Anda.

Misalnya, jika Anda hanya perlu membuat String dari objek Anda, Anda dapat menggunakan adaptor seperti ini:

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

dan daftarkan seperti ini:

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

atau seperti ini, jika Anda memiliki antarmuka dan ingin menggunakan adaptor untuk semua subkelasnya:

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

Jawaban saya agak terlambat, tetapi menurut saya pertanyaan ini belum memiliki solusi yang baik. Saya menemukannya awalnya di sini .

Dengan Gson Anda dapat menandai bidang yang ingin Anda masukkan ke dalam json dengan @Exposeseperti ini:

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

dan buat objek gson dengan:

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

Referensi melingkar yang tidak Anda ungkapkan. Itu berhasil untuk saya!

ffonz.dll
sumber
Tahukah Anda jika ada anotasi yang melakukan kebalikan dari ini? Ada 4 bidang yang harus saya abaikan dan lebih dari 30 yang perlu saya sertakan.
jDub9
@ jDub9 Maaf atas jawaban saya yang terlambat, tapi saya sedang berlibur. Lihat jawaban ini . Semoga ini menyelesaikan masalah Anda
ffonz
3

Kesalahan ini biasa terjadi ketika Anda memiliki logger di kelas super Anda. Seperti yang disarankan @Zar sebelumnya, Anda dapat menggunakan statis untuk bidang logger Anda, tetapi ini juga berfungsi:

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

PS mungkin akan berfungsi dan dengan penjelasan @Expose, periksa lebih lanjut tentang ini di sini: https://stackoverflow.com/a/7811253/1766166

zygimantus
sumber
1

Saya memiliki masalah yang sama. Dalam kasus saya, alasannya adalah konstruktor kelas serial saya mengambil variabel konteks, seperti ini:

public MetaInfo(Context context)

Ketika saya menghapus argumen ini, kesalahan telah hilang.

public MetaInfo()
Denis
sumber
1
Saya mengalami masalah ini saat meneruskan referensi objek layanan sebagai konteks. Perbaiki adalah membuat variabel konteks menjadi statis di kelas yang menggunakan gson.toJson (this).
pengguna802467
@ user802467 maksud Anda layanan android?
Preetam
1

Edit: Maaf atas kesalahan saya, ini adalah jawaban pertama saya. Terima kasih atas saran Anda.

Saya membuat Konverter Json saya sendiri

Solusi utama yang saya gunakan adalah membuat set objek orang tua untuk setiap referensi objek. Jika sub-referensi menunjuk ke objek induk yang ada, itu akan dibuang. Kemudian saya kombinasikan dengan solusi ekstra, membatasi waktu referensi untuk menghindari loop infinitif dalam hubungan dua arah antar entitas.

Uraian saya kurang bagus, semoga bisa membantu kalian.

Ini adalah kontribusi pertama saya untuk komunitas Java (solusi untuk masalah Anda). Anda dapat memeriksanya;) Ada file README.md https://github.com/trannamtrung1st/TSON

Trần Nam Trung
sumber
2
Tautan ke solusi diperbolehkan, tetapi harap pastikan jawaban Anda berguna tanpanya: tambahkan konteks di sekitar tautan sehingga sesama pengguna Anda akan tahu apa itu dan mengapa itu ada, kemudian kutip bagian paling relevan dari halaman Anda ' menautkan ulang jika halaman target tidak tersedia. Jawaban yang tidak lebih dari sebuah tautan dapat dihapus.
Paul Roub
2
Promosi Diri Hanya menautkan ke perpustakaan atau tutorial Anda sendiri bukanlah jawaban yang baik. Menautkan ke sana, menjelaskan mengapa itu menyelesaikan masalah, memberikan kode tentang bagaimana melakukannya dan menyangkal bahwa Anda menulisnya membuat jawaban yang lebih baik. Lihat: Apa yang menandakan promosi diri yang "Baik"?
Shree
Terima kasih banyak. Saya telah mengedit jawaban saya. Berharap akan baik-baik saja: D
Trần Nam Trung
Mirip dengan apa yang dikatakan pemberi komentar lain, lebih disukai Anda menunjukkan bagian terpenting dari kode Anda di posting Anda. Selain itu, Anda tidak perlu meminta maaf atas kesalahan dalam jawaban Anda.
0xCursor
0

Di Android, gson stack overflow ternyata adalah deklarasi Handler. Memindahkannya ke kelas yang tidak sedang deserialisasi.

Berdasarkan rekomendasi Zar, saya membuat pawang menjadi statis ketika ini terjadi di bagian kode lainnya. Membuat pawang statis juga berfungsi.

Dan
sumber
0

BomItemmengacu pada BOMModule( Collection<BomModule> modules), dan BOMModulemengacu pada BOMItem( Collection<BomItem> items). Perpustakaan Gson tidak menyukai referensi melingkar. Hapus ketergantungan melingkar ini dari kelas Anda. Saya juga pernah menghadapi masalah yang sama di masa lalu dengan gson lib.

Binita Bharati
sumber
0

Saya mengalami masalah ini ketika saya meletakkan:

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

dalam objek saya ... yang sangat masuk akal setelah satu jam atau lebih debugging!

keesp
sumber
0

Untuk pengguna Android, Anda tidak dapat membuat serial Bundlekarena referensi mandiri yang Bundlemenyebabkan aStackOverflowError .

Untuk membuat serial bundel, daftarkan aBundleTypeAdapterFactory .

Cord Rehn
sumber
0

Hindari solusi yang tidak perlu, seperti menyetel nilai ke null atau membuat kolom menjadi sementara. Cara yang tepat untuk melakukannya adalah dengan memberi anotasi pada salah satu bidang dengan @Expose dan kemudian memberi tahu Gson untuk membuat serial hanya bidang dengan anotasi:

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

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

Saya memiliki masalah serupa di mana kelas memiliki variabel InputStream yang sebenarnya tidak harus saya pertahankan. Oleh karena itu mengubahnya menjadi Transient memecahkan masalah.

Kamalakannan V
sumber
0

Setelah beberapa lama berjuang dengan masalah ini, saya yakin saya punya solusi. Masalahnya ada pada koneksi dua arah yang tidak terselesaikan, dan bagaimana merepresentasikan koneksi ketika mereka sedang dibuat serial. Cara untuk memperbaiki perilaku tersebut adalah dengan "memberi tahu" gsoncara membuat serial objek. Untuk tujuan itu kami gunakanAdapters .

Dengan menggunakan Adapterskami dapat mengetahui gsoncara membuat serial setiap properti dari Entitykelas Anda serta properti mana yang akan diserialkan.

Membiarkan Foodan Barmenjadi dua entitas yang Foomemiliki OneToManyhubungan Bardan Barmemiliki ManyToOnehubungan Foo. Kami mendefinisikan Baradaptor sehingga ketika membuat gsonserial Bar, dengan menentukan bagaimana membuat serial Foodari perspektif Barreferensi siklik tidak akan mungkin.

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

Di sini foo_iddigunakan untuk mewakili Fooentitas yang akan diserialisasi dan yang akan menyebabkan masalah referensi siklik. Nah bila kita menggunakan adapter Footidak akan diserialisasi lagi dari Barhanya id nya saja yang akan diambil dan dimasukkan JSON. Sekarang kami memiliki Baradaptor dan kami dapat menggunakannya untuk membuat serial Foo. Inilah idenya:

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

Satu hal lagi yang perlu diperjelas, foo_idtidak wajib dan dapat dilewati. Tujuan adaptor dalam contoh ini adalah untuk membuat serial Bardan dengan menempatkan foo_idkami menunjukkan bahwa Bardapat memicu ManyToOnetanpa menyebabkan Foomemicu OneToManylagi ...

Jawaban berdasarkan pengalaman pribadi, oleh karena itu silakan berkomentar, membuktikan saya salah, memperbaiki kesalahan, atau memperluas jawaban. Bagaimanapun saya berharap seseorang akan menemukan jawaban ini berguna.

quepasa
sumber
Menentukan adaptor untuk menangani proek serialisasi itu sendiri adalah cara lain untuk menangani ketergantungan siklik. Anda memiliki opsi untuk itu meskipun ada penjelasan lain yang dapat mencegah hal ini terjadi daripada menulis adaptor.
Sariq Shaikh