web-dev-qa-db-ja.com

gson.toJson()は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;

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

71
nimrod

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

BomModuleクラスで参照しているもの:

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

BomModuleへの自己参照は、明らかに、GSONにはまったく好まれていません。

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

item.setModules(null);

または、transientキーワードを使用して、シリアル化されたJSONに表示する不要のフィールドをマークします。例:

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

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

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

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

27
Zar

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

コピーされるオブジェクトの束の中の1つだけでこれを行うのを逃していました。つまり、問題は循環参照によって引き起こされますが、それはRealmObjectベースクラスのどこかにある循環参照であり、独自のサブクラスではないため、見つけるのが難しくなります!

19
Breeno

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

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

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

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

そして、次を使用してgsonオブジェクトを作成します。

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

あなたがちょうど公開しない循環参照。それは私のためにトリックをしました!

7
ffonz

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

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

追伸おそらく動作し、@ Exposeアノテーションを使用して、これについて詳細をチェックします。 https://stackoverflow.com/a/7811253/1766166

2
zygimantus

編集:私の悪いため申し訳ありませんが、これは私の最初の答えです。ご助言ありがとうございます。

独自のJson Converterを作成します

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

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

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

1

私は同じ問題を抱えています。私の場合、その理由は、次のように、シリアライズされたクラスのコンストラクターがコンテキスト変数を取ることです。

public MetaInfo(Context context)

この引数を削除すると、エラーが発生しました。

public MetaInfo()
1
Denis

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

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

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

Androidユーザーの場合、Bundleへの自己参照によりBundleが発生するため、StackOverflowErrorをシリアル化できません。

バンドルをシリアル化するには、 BundleTypeAdapterFactoryを登録

0
Cord Rehn

BomItemBOMModuleCollection<BomModule> modules)、およびBOMModuleBOMItemCollection<BomItem> items)。 Gsonライブラリは循環参照を好みません。この循環依存関係をクラスから削除します。私も過去にgson libで同じ問題に直面していました。

0
Binita Bharati

クラスにInputStream変数があるという同様の問題がありましたが、実際には永続化する必要はありませんでした。したがって、一時的に変更することで問題は解決しました。

0
Kamalakannan V

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

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

0
Dan

私が入れたときにこの問題が発生しました:

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

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

0
keesp