gson.toJson()はStackOverflowErrorをスローします


87

オブジェクトから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;

このエラーの原因は何ですか?どうすれば修正できますか?


gson内のどこかにオブジェクトインスタンスを配置すると発生する可能性があります。
Christophe Roussy 2015年

例外JsonWriter.java:473)は根本原因を失い、ログを開始します。Gsonスタックオーバーフローの根本原因を特定するにはどうすればよいですか
Siddharth

回答:


86

その問題は、循環参照があることです。

BomModule参照しているクラスでは、次のようになります。

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

BomModule明らかに、GSONにはまったく好まれていないという自己参照。

回避策はnull、再帰的なループを回避するようにモジュールを設定するだけです。このようにして、StackOverFlow-Exceptionを回避できます。

item.setModules(null);

または、キーワードを使用して、シリアル化されたjsonに表示したくないフィールドをマークします。transient例:

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

はい、BomModuleオブジェクトは別のBomModuleオブジェクトの一部にすることができます。
nimrod 2012

しかし、それは問題ですか?'Collection <BomModule> modules'は単なるコレクションであり、gsonはそれから単純な配列を作成できるはずだと思いますか?
nimrod 2012

@dooonot:コレクション内のオブジェクトのいずれかが親オブジェクトを参照していますか?
SLaks 2012

私があなたを正しく理解したかどうかはわかりませんが、そうです。上記の更新された質問を参照してください。
nimrod 2012

@dooonot:私が推測したように、親コレクションと子コレクションをシリアル化すると、無限ループに入ります。どのようなJSONを作成すると思いますか?
SLaks 2012

29

次のようなクラスプロパティとしてLog4Jロガーを使用したときに、この問題が発生しました。

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

これは、ロガーを作成するstaticか、単に実際の関数に移動することで解決できます。


4
絶対に素晴らしいキャッチ。クラスへのその自己参照は、明らかにGSONにはまったく好まれていません。私に多くの頭痛を救った!+1
クリストファー2014年

1
それを解決する別の方法は、フィールドに一時的な修飾子を追加することです
gawi 2016年

ロガーはほとんど静的である必要があります。そうしないと、オブジェクトの作成ごとにそのLoggerインスタンスを取得するコストが発生しますが、これはおそらく必要なものではありません。(コストはささいなことではありません)
stolsvik 2018

26

Realmを使用していて、このエラーが発生し、問題を引き起こしているオブジェクトがRealmObjectを拡張する場合は、realm.copyFromRealm(myObject)シリアル化のためにGSONに渡す前に、すべてのRealmバインディングなしでコピーを作成することを忘れないでください。

コピーされているオブジェクトの束のうちの1つだけでこれを行うのを逃しました...スタックトレースがオブジェクトのクラス/タイプを指定していないため、気付くのに何年もかかりました。問題は循環参照が原因ですが、これはRealmObject基本クラスのどこかにある循環参照であり、独自のサブクラスではないため、見つけるのが難しくなります。


1
そのとおりです!私の場合、直接クエリされたオブジェクトリストをレルムからArrayList <Image> copyList = new ArrayList <>();に変更します。for(画像画像:画像){copyList.add(realm.copyFromRealm(image)); }
リッカルドムーティ2017年

レルムを使用して、それはまさに問題を解決する解決策でした、ありがとう
Jude Fernandes

13

SLaksが言ったように、オブジェクトに循環参照がある場合、StackOverflowErrorが発生します。

これを修正するには、オブジェクトにTypeAdapterを使用できます。

たとえば、オブジェクトから文字列を生成するだけでよい場合は、次のようなアダプタを使用できます。

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

9

私の答えは少し遅れていますが、この質問にはまだ良い解決策がないと思います。もともとここで見つけまし

Gsonを使用すると、jsonに含めたいフィールドを次のようにマークできます@Expose

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

次のコマンドでgsonオブジェクトを作成します。

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

公開しない循環参照。それは私にとってトリックでした!


これとは逆の注釈があるかどうか知っていますか?無視する必要のある4つのフィールドと、含める必要のある30を超えるフィールドがあります。
jDub9 2018

@ jDub9回答が遅れて申し訳ありませんが、私は休暇中です。この答えを見てください。それがあなたの問題を解決することを願っています
ffonz 2018

3

このエラーは、スーパークラスにロガーがある場合によく発生します。@Zarが以前に提案したように、ロガーフィールドにstaticを使用できますが、これも機能します。

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

PSおそらくそれは動作し、@ Exposeアノテーションでこれについてもっとチェックしてください:https://stackoverflow.com/a/7811253/1766166


1

私も同じ問題を抱えてる。私の場合、その理由は、シリアル化されたクラスのコンストラクターが次のようなコンテキスト変数を受け取るためです。

public MetaInfo(Context context)

この引数を削除すると、エラーがなくなりました。

public MetaInfo()

1
サービスオブジェクト参照をコンテキストとして渡すときにこの問題が発生しました。修正は、gson.toJson(this)を使用するクラスでコンテキスト変数を静的にすることでした。
user802467 2014年

@ user802467 Androidサービスのことですか?
Preetam 2016年

1

編集:私の悪いことを申し訳ありませんが、これは私の最初の答えです。あなたのアドバイスをありがとう。

独自のJsonConverterを作成します

私が使用した主な解決策は、オブジェクト参照ごとに親オブジェクトセットを作成することです。サブ参照が既存の親オブジェクトを指している場合、それは破棄されます。次に、追加のソリューションと組み合わせて、エンティティ間の双方向の関係で無限ループが発生しないように参照時間を制限します。

私の説明はあまり良くありません、それが皆さんに役立つことを願っています。

これは、Javaコミュニティへの私の最初の貢献です(あなたの問題の解決策)。あなたはそれをチェックすることができます;)README.mdファイルがあります https://github.com/trannamtrung1st/TSON


2
ソリューションへのリンクは大歓迎ですが、それがなくても回答が役立つことを確認してください。リンクの周りにコンテキストを追加して、他のユーザーがそれが何であるか、なぜそこにあるのかを理解できるようにしてから、ページの最も関連性の高い部分を引用してください。ターゲットページが利用できない場合に再リンクします。リンクに過ぎない回答は削除される場合があります。
Paul Roub 2018

2
自己宣伝自分のライブラリやチュートリアルにリンクするだけでは良い答えではありません。それにリンクし、それが問題を解決する理由を説明し、それを行う方法に関するコードを提供し、あなたがそれを書いたことを放棄することは、より良い答えになります。参照: 「良い」自己宣伝を意味するものは何ですか?
シュリー2018

本当にありがとう。私は自分の答えを編集しました。D:それは大丈夫だと思うホープ
TRANナムチュン

他のコメント投稿者が言ったことと同様に、コードの最も重要な部分を投稿に表示することをお勧めします。また、答えの間違いについて謝罪する必要はありません。
0xCursor 2018

0

Androidでは、gsonスタックオーバーフローがハンドラーの宣言であることが判明しました。デシリアライズされていないクラスに移動しました。

Zarの推奨に基づいて、コードの別のセクションでこれが発生したときに、ハンドラーを静的にしました。ハンドラーを静的にすることも同様に機能しました。


0

BomItemいうBOMModuleCollection<BomModule> modules)、及びBOMModule指しますBOMItemCollection<BomItem> items)。Gsonライブラリは循環参照が好きではありません。この循環依存関係をクラスから削除します。私も過去にgsonlibで同じ問題に直面していました。


0

私が置いたときに私はこの問題が発生しました:

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

私のオブジェクトでは... 1時間ほどのデバッグの後で完全に理にかなっています!



0

値をnullに設定したり、フィールドを一時的にしたりするなど、不要な回避策は避けてください。これを行う正しい方法は、フィールドの1つに@Exposeで注釈を付けてから、注釈が付いたフィールドのみをシリアル化するようにGsonに指示することです。

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

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

0

クラスにInputStream変数があり、実際に永続化する必要がないという同様の問題がありました。したがって、これをTransientに変更すると、問題が解決しました。


0

この問題としばらく戦った後、私は解決策があると信じています。問題は、未解決の双方向接続と、シリアル化されているときに接続を表す方法にあります。この動作を修正する方法は、gsonオブジェクトをシリアル化する方法を「伝える」ことです。そのために、を使用しますAdapters

を使用AdaptersすることでgsonEntityクラスのすべてのプロパティをシリアル化する方法と、シリアル化するプロパティを知ることができます。

ましょうFooBar2つのエンティティもFoo持っているOneToManyとの関連をBarしてBar持っているManyToOneとの関係をFooBarアダプターを定義するので、gsonシリアル化するときに、循環参照の観点Barからシリアル化する方法を定義することはできません。FooBar

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シリアライズされるだろうと私たちの巡回参照の問題を引き起こすことになるエンティティを。これで、アダプタを使用するFooBar、その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;
}

もう1つ明確にすることfoo_idは必須ではなく、スキップできます。この例のアダプターの目的はシリアルBar化することであり、置くfoo_idことによって、再度トリガーBarすることManyToOneなくトリガーできることを示しました...FooOneToMany

回答は個人的な経験に基づいているため、コメントしたり、間違っていることを証明したり、間違いを修正したり、回答を拡大したりしてください。とにかく、誰かがこの答えが役に立つと思うことを願っています。


ハンドルシリアル化プロセス自体のアダプターを定義することは、循環依存を処理する別の方法です。アダプターを作成する代わりに、これを防ぐことができる他の注釈がありますが、それに対するオプションがあります。
Sariq Shaikh
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.