gson.toJson (), StackOverflowError'ı atar


87

Nesnemden bir JSON Dizesi oluşturmak istiyorum:

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

Bunu her yapmaya çalıştığımda şu hatayı alıyorum:

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)

Bunlar BomItem sınıfımın öznitelikleridir :

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;

Referans verilen BomModule sınıfımın öznitelikleri :

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

Bu hataya neyin sebep olduğuna dair bir fikriniz var mı? Nasıl düzeltebilirim?


Gson içinde bir yere kendi içinde bir nesne örneği koyarsanız gerçekleşebilir.
Christophe Roussy

İstisna, temel nedeni kaybeder ve günlüğü başlatır JsonWriter.java:473), Gson yığın aşırı akışının temel nedeni nasıl belirlenir
Siddharth

Yanıtlar:


86

Bu sorun, döngüsel bir referansa sahip olmanızdır.

In BomModulesınıfa size başvuran:

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

Yani öz referans için BomModule, belli ki, hiç GSON tarafından sevilmiyor.

Bir geçici çözüm, modülleri nullözyinelemeli döngüden kaçınmak için ayarlamanız yeterlidir. Bu şekilde StackOverFlow-Exception'dan kaçınabilirim.

item.setModules(null);

Veya serileştirilmiş json'da görünmesini istemediğiniz alanları transientanahtar kelimeyi kullanarak işaretleyin , örneğin:

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

Evet, bir BomModule nesnesi başka bir BomModule nesnesinin parçası olabilir.
nimrod

Ama bu bir sorun mu? 'Koleksiyon <BomModule> modülleri' yalnızca bir koleksiyondur ve bence gson bundan basit bir dizi oluşturabilir mi?
nimrod

@dooonot: Koleksiyondaki nesnelerden herhangi biri üst nesnesine başvuruyor mu?
SLaks

Seni doğru anladığımdan emin değilim ama evet. Lütfen yukarıdaki güncellenmiş soruya bakın.
nimrod

@dooonot: Şüphelendiğim gibi, ebeveyn ve çocuk koleksiyonlarını serileştirirken sonsuz bir döngüye giriyor. Ne tür bir JSON yazmasını bekliyorsunuz?
SLaks

30

Sınıf özelliği olarak Log4J logger'ım olduğunda bu sorunu yaşadım, örneğin:

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

Bu, kaydediciyi yaparak staticveya basitçe onu gerçek işlev (ler) e taşıyarak çözülebilir .


4
Kesinlikle harika bir av. Sınıfa yapılan bu öz referans, açıkça GSON tarafından hiç beğenilmiyor. Beni çok fazla baş ağrısından kurtardı! +1
christopher

1
Bunu çözmenin başka bir yolu, alana geçici değiştirici eklemektir
gawi

logger çoğunlukla statik olmalıdır. Aksi takdirde, her nesne oluşturma için Logger örneğini edinme maliyetine katlanacaksınız, ki bu muhtemelen istediğiniz şey değildir. (Maliyet önemsiz değil)
stolsvik

26

Realm kullanıyorsanız ve bu hatayı alırsanız ve sorun çıkaran nesne RealmObject'i genişletirse, realm.copyFromRealm(myObject)serileştirme için GSON'a geçmeden önce tüm Realm bağlamaları olmadan bir kopya oluşturmayı unutmayın .

Bunu, kopyalanan bir grup nesneden sadece biri için yapmayı özlemiştim ... yığın izinin nesne sınıfını / türünü adlandırmadığını fark etmem yaşlarımı aldı. Mesele şu ki, sorun döngüsel bir referanstan kaynaklanıyor, ancak bu kendi alt sınıfınız değil, RealmObject temel sınıfında bir yerde döngüsel bir referanstır ve bu da fark edilmesini zorlaştırır!


1
Bu doğru! Benim durumumda, doğrudan bölgeden sorgulanan nesne listemi ArrayList <Image> copyList = new ArrayList <> (); için (Resim resmi: resimler) {copyList.add (realm.copyFromRealm (resim)); }
Ricardo Mutti

Bölge kullanarak, sorunu çözen tam çözüm
Jude Fernandes

13

SLaks'ın dediği gibi StackOverflowError, nesnenizde dairesel referans varsa gerçekleşir.

Düzeltmek için nesneniz için TypeAdapter kullanabilirsiniz.

Örneğin, nesnenizden yalnızca String oluşturmaya ihtiyacınız varsa, şu şekilde bağdaştırıcı kullanabilirsiniz:

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

ve şu şekilde kaydedin:

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

veya bunun gibi, arayüzünüz varsa ve tüm alt sınıfları için adaptör kullanmak istiyorsanız:

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

9

Cevabım biraz geç ama bu sorunun henüz iyi bir çözümü olmadığını düşünüyorum. Aslen burada buldum .

GSON ile size alanları işaretleyebilirsiniz do ile json dahil olmak istiyorum @Exposeböyle:

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

ve gson nesnesini şununla oluşturun:

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

Sadece Dairesel referanslar yok maruz. Bu benim için hile yaptı!


Bunun tersini yapan bir ek açıklama olup olmadığını biliyor musunuz? Göz ardı etmem gereken 4 alan ve dahil etmem gereken 30'dan fazla alan var.
jDub9

@ jDub9 Geç cevabım için özür dilerim, ama tatildeydim. Bu cevaba bir göz atın . Umarım sorununuzu çözer
ffonz

3

Bu hata, süper sınıfınızda bir kaydedici olduğunda yaygındır. @Zar'ın daha önce önerdiği gibi , logger alanınız için statik kullanabilirsiniz , ancak bu da işe yarar :

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

PS muhtemelen işe yarayacak ve @Expose ek açıklama ile bunun hakkında daha fazla kontrol edin: https://stackoverflow.com/a/7811253/1766166


1

Bende de aynı sorun var. Benim durumumda sebep, serileştirilmiş sınıfımın kurucusunun bağlam değişkenini almasıydı, şöyle ki:

public MetaInfo(Context context)

Bu argümanı sildiğimde hata gitti.

public MetaInfo()

1
Hizmet nesnesi başvurusunu bağlam olarak iletirken bu sorunla karşılaştım. Düzeltme, gson.toJson (bu) kullanan sınıfta bağlam değişkenini statik yapmaktı.
user802467

@ user802467 android hizmeti mi demek istiyorsun?
Preetam

1

Düzenleme: Hatam için üzgünüm, bu benim ilk cevabım. Tavsiyelerin için teşekkürler.

Kendi Json Converter'ımı yaratıyorum

Kullandığım ana çözüm, her nesne referansı için bir üst nesne kümesi oluşturmaktır. Bir alt referans var olan üst nesneye işaret ederse, atılır. Daha sonra, varlıklar arasındaki çift yönlü ilişkide sonsuz döngüden kaçınmak için referans süresini sınırlayarak ekstra bir çözümle birleştiriyorum.

Tanımım pek iyi değil, umarım size yardımcı olur.

Bu, Java topluluğuna ilk katkım (sorununuzun çözümü). Kontrol edebilirsiniz;) Bir README.md dosyası var https://github.com/trannamtrung1st/TSON


2
Bir çözüme bağlantı memnuniyetle karşılanır, ancak lütfen cevabınızın o olmadan yararlı olduğundan emin olun: Bağlantının etrafına bağlam ekleyin, böylece kullanıcı arkadaşlarınız bunun ne olduğu ve neden orada olduğu konusunda fikir sahibi olurlar, ardından sayfanın en alakalı bölümünü alıntılayın ' hedef sayfanın mevcut olmaması durumunda yeniden bağlanma. Bir bağlantıdan biraz daha fazla olan cevaplar silinebilir.
Paul Roub

2
Kendi Kendini Tanıtma Kendi kitaplığınıza veya eğiticinize bağlanmanız iyi bir cevap değildir. Buna bağlanmak, sorunu neden çözdüğünü açıklamak, nasıl yapılacağına dair kod sağlamak ve yazdığınızı reddetmek daha iyi bir yanıt sağlar. Bakınız: Kendini tanıtmanın “iyi” anlamına gelen nedir?
Shree

Çok teşekkürler. Cevabımı düzenledim. Umarım iyi olur: D
Trần Nam Trung

Diğer yorumcuların söylediğine benzer şekilde, kodunuzun en önemli kısımlarını gönderinizde göstermeniz tercih edilir. Ayrıca cevabınızdaki hatalar için özür dilemenize gerek yok.
0xCursor

0

Android'de, gson yığın taşması bir İşleyicinin beyanı olduğu ortaya çıktı. Seri durumdan çıkarılmayan bir sınıfa taşıdı.

Zar'ın tavsiyesine dayanarak, kodun başka bir bölümünde bu gerçekleştiğinde işleyiciyi statik hale getirdim. İşleyiciyi statik yapmak da işe yaradı.


0

BomItemifade eder BOMModule( Collection<BomModule> modules) ve BOMModulekarşılık gelir BOMItem( Collection<BomItem> items). Gson kitaplığı döngüsel başvuruları sevmez. Bu döngüsel bağımlılığı sınıfınızdan kaldırın. Ben de geçmişte gson lib ile aynı sorunla karşılaşmıştım.


0

Bu sorunu koyduğumda benim için ortaya çıktı:

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

nesnemde ... bu bir saat kadar hata ayıklamadan sonra mükemmel bir anlam ifade etti!



0

Değerleri boşa ayarlamak veya alanları geçici yapmak gibi gereksiz geçici çözümlerden kaçının. Bunu yapmanın doğru yolu, alanlardan birine @Expose ile açıklama eklemek ve ardından Gson'a yalnızca açıklamanın bulunduğu alanları serileştirmesini söylemektir:

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

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

0

Sınıfın gerçekten devam etmem gereken bir InputStream değişkenine sahip olduğu benzer bir sorun yaşadım. Dolayısıyla bunu Geçici olarak değiştirmek sorunu çözdü.


0

Bir süre bu konuyla mücadele ettikten sonra bir çözümüm olduğuna inanıyorum. Sorun çözülmemiş çift yönlü bağlantılarda ve serileştirilirken bağlantıların nasıl temsil edileceğidir. Bu davranışı düzeltmenin yolu, gsonnesnelerin nasıl serileştirileceğini "söylemektir" . Bu amaçla kullanıyoruz Adapters.

Kullanarak , sınıfınızdaki her özelliğin nasıl serileştirileceğini ve hangi özelliklerin serileştirileceğini Adapterssöyleyebiliriz .gsonEntity

Let Foove Bariki taraf olmak Foovardır OneToManyiçin ilişkiyi Barve Barsahip ManyToOneüzere ilişkisini Foo. BarBağdaştırıcı tanımlıyoruz , böylece gsonserileştirirken Bar, döngüsel referanslama Fooaçısından nasıl serileştirileceğini tanımlayarak Barmümkün olmayacak.

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

Burada foo_id, Fooserileştirilecek ve döngüsel referanslama sorunumuza neden olacak varlığı temsil etmek için kullanılır . Artık bağdaştırıcı kullandığımızda Footekrar serileştirilmeyecek, Barsadece kimliği alınıp yerleştirilecek JSON. Şimdi Baradaptörümüz var ve onu serileştirmek için kullanabiliriz Foo. İşte fikir:

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

Açıklığa kavuşturulması gereken bir şey daha foo_idzorunlu değildir ve atlanabilir. Bu örnekte adaptörün amacı serileştirmek Barve tekrar tetiklemeye neden olmadan tetikleyebileceğini foo_idgösterdik ...BarManyToOneFooOneToMany

Cevap kişisel deneyime dayanmaktadır, bu nedenle yorum yapmaktan, yanlış olduğumu kanıtlamaktan, hataları düzeltmekten veya cevabı genişletmekten çekinmeyin. Her neyse, umarım birisi bu cevabı faydalı bulacaktır.


Tanıtıcı serileştirme süreci için adaptör tanımlamak, döngüsel bağımlılığı ele almanın başka bir yoludur. Adaptörleri yazmak yerine bunun olmasını engelleyebilecek başka açıklamalar olsa da bunun için seçeneğiniz var.
Sariq Shaikh
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.