簡體   English   中英

gson.toJson() 拋出 StackOverflowError

[英]gson.toJson() throws StackOverflowError

我想從我的對象生成一個 JSON 字符串:

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

每次我嘗試這樣做時,都會收到此錯誤:

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)

這些是我的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;

我引用的BomModule類的屬性:

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

知道是什么導致了這個錯誤嗎? 我該如何解決?

那個問題是你有一個循環引用。

BomModule類中,您引用了:

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

顯然,GSON 根本不喜歡對BomModule自我引用。

解決方法是將模塊設置為null以避免遞歸循環。 這樣我就可以避免 StackOverFlow-Exception。

item.setModules(null);

或者使用transient關鍵字標記您不想在序列化json中顯示的字段,例如:

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

當我將 Log4J 記錄器作為類屬性時,我遇到了這個問題,例如:

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

這可以通過使記錄器static或簡單地將其移動到實際函數中來解決。

如果您正在使用 Realm 並且遇到此錯誤,並且出現問題的對象擴展了 RealmObject,請不要忘記執行realm.copyFromRealm(myObject)在傳遞給 GSON 進行序列化之前創建一個沒有所有 Realm 綁定的副本。

我錯過了為正在復制的一堆對象中的一個做這件事......花了我很長時間才意識到,因為堆棧跟蹤沒有命名對象類/類型。 問題是,問題是由循環引用引起的,但它是 RealmObject 基類中某處的循環引用,而不是您自己的子類,這使得它更難發現!

正如 SLaks 所說,如果您的對象中有循環引用,就會發生 StackOverflowError。

要修復它,您可以為您的對象使用 TypeAdapter。

例如,如果你只需要從你的對象生成 String 你可以像這樣使用適配器:

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

並像這樣注冊:

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

或者像這樣,如果您有接口並希望為其所有子類使用適配器:

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

我的回答有點晚了,但我認為這個問題還沒有很好的解決方案。 我最初是在這里找到的。

隨着GSON你可以標記想被列入使用JSON領域@Expose是這樣的:

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

並使用以下命令創建 gson 對象:

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

您只是公開的循環引用。 這對我有用!

當您的超類中有記錄器時,此錯誤很常見。 正如@Zar 之前建議的那樣,您可以對記錄器字段使用靜態,但這也有效:

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

PS 可能它會起作用,並使用 @Expose 注釋在此處檢查更多相關信息: https ://stackoverflow.com/a/7811253/1766166

我也有同樣的問題。 就我而言,原因是我的序列化類的構造函數采用上下文變量,如下所示:

public MetaInfo(Context context)

當我刪除這個參數時,錯誤消失了。

public MetaInfo()

編輯:對不起,這是我的第一個答案。 謝謝你的建議。

我創建了自己的 Json 轉換器

我使用的主要解決方案是為每個對象引用創建一個父對象集。 如果子引用指向存在的父對象,它將被丟棄。 然后我結合了一個額外的解決方案,限制參考時間以避免實體之間雙向關系中的無限循環。

我的描述不太好,希望對大家有幫助。

這是我對 Java 社區的第一次貢獻(解決您的問題)。 你可以看看;) 有一個 README.md 文件https://github.com/trannamtrung1st/TSON

對於Android用戶,你不能序列化Bundle由於自我參照Bundle造成StackOverflowError

要序列化包,請注冊一個BundleTypeAdapterFactory

在Android中,gson堆棧溢出原來是一個Handler的聲明。 將其移至未反序列化的類。

根據 Zar 的建議,當這發生在另一段代碼中時,我將處理程序設為靜態。 使處理程序靜態也有效。

BomItem指的是BOMModule ( Collection<BomModule> modules ),而BOMModule指的是BOMItem ( Collection<BomItem> items )。 Gson 庫不喜歡循環引用。 從您的類中刪除此循環依賴項。 過去我也遇到過與 gson lib 相同的問題。

當我放置時,我遇到了這個問題:

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

在我的對象中......經過一個小時左右的調試后,這非常有意義!

避免不必要的解決方法,例如將值設置為 null 或使字段變得瞬態。 正確的方法是用@Expose 注釋其中一個字段,然后告訴 Gson 只序列化帶有注釋的字段:

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

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

我有一個類似的問題,該類有一個 InputStream 變量,我實際上不必堅持。 因此將其更改為 Transient 解決了該問題。

經過一段時間與這個問題的斗爭,我相信我有一個解決方案。 問題在於未解決的雙向連接,以及如何在序列化時表示連接。 修復該行為的方法是“告訴” gson如何序列化對象。 為此,我們使用Adapters

通過使用Adapters我們可以告訴gson如何序列化Entity類中的每個屬性以及序列化哪些屬性。

FooBar是兩個實體,其中Foo具有OneToMany的關系,以BarBarManyToOne關系Foo 我們定義了Bar適配器,所以當gson序列化Bar ,通過定義如何從Bar循環引用的角度序列化Foo將是不可能的。

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

這里foo_id用於表示將被序列化並會導致我們的循環引用問題的Foo實體。 現在當我們使用適配器Foo將不會從Bar再次序列化,只會獲取它的 id 並將其放入JSON 現在我們有了Bar適配器,我們可以用它來序列化Foo 這是想法:

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

還有一件事要澄清, foo_id不是強制性的,可以跳過。 在這個例子中適配器的目的是序列化Bar並且通過放置foo_id我們展示了Bar可以觸發ManyToOne而不會導致Foo再次觸發OneToMany ...

答案是基於個人經驗,因此請隨時發表評論,證明我錯了,糾正錯誤,或擴大答案。 無論如何,我希望有人會發現這個答案很有用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM