97

我想从我的对象生成一个 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;

知道是什么导致了这个错误吗?我该如何解决?

4

15 回答 15

93

那个问题是你有一个循环引用。

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;
于 2012-04-18T13:05:05.580 回答
31

当我将 Log4J 记录器作为类属性时,我遇到了这个问题,例如:

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

这可以通过制作记录器static或简单地将其移动到实际函数中来解决。

于 2013-01-28T22:02:16.700 回答
27

如果您正在使用 Realm 并且遇到此错误,并且出现问题的对象扩展了 RealmObject,请不要忘记realm.copyFromRealm(myObject)在传递给 GSON 进行序列化之前创建一个没有所有 Realm 绑定的副本。

我错过了为一堆被复制的对象中的一个做这个......我花了很长时间才意识到堆栈跟踪没有命名对象类/类型。问题是,问题是由循环引用引起的,但它是 RealmObject 基类中某处的循环引用,而不是您自己的子类,这使得它更难被发现!

于 2016-09-23T22:36:20.160 回答
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();
于 2013-07-15T17:11:02.463 回答
10

我的回答有点晚了,但我认为这个问题还没有很好的解决方案。我最初是在这里找到的。

使用 Gson,您可以像这样标记您希望包含在 json 中的字段@Expose

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

并使用以下命令创建 gson 对象:

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

您只是不公开的循环引用。这对我有用!

于 2017-12-06T12:58:29.570 回答
3

当您的超类中有记录器时,此错误很常见。正如@Zar 之前建议的那样,您可以将静态用于您的记录器字段,但这也有效:

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

PS可能会起作用,并使用@Expose注释在此处检查更多信息:https ://stackoverflow.com/a/7811253/1766166

于 2015-05-22T07:09:39.233 回答
1

我也有同样的问题。就我而言,原因是我的序列化类的构造函数采用上下文变量,如下所示:

public MetaInfo(Context context)

当我删除这个参数时,错误就消失了。

public MetaInfo()
于 2014-06-20T04:21:12.560 回答
1

编辑:对不起,这是我的第一个答案。感谢您的建议。

我创建了自己的 Json 转换器

我使用的主要解决方案是为每个对象引用创建一个父对象集。如果子引用指向已存在的父对象,它将被丢弃。然后我结合了一个额外的解决方案,限制参考时间以避免实体之间的双向关系中的不定式循环。

我的描述不太好,希望对大家有所帮助。

这是我对 Java 社区的第一个贡献(解决您的问题)。你可以检查一下;)有一个 README.md 文件 https://github.com/trannamtrung1st/TSON

于 2018-07-23T04:11:02.360 回答
1

对于 Android 用户,Bundle由于自引用Bundle导致StackOverflowError.

要序列化捆绑包,请注册一个BundleTypeAdapterFactory.

于 2018-12-31T16:16:59.083 回答
0

在 Android 中,gson 堆栈溢出原来是一个 Handler 的声明。将其移至未反序列化的类。

根据 Zar 的建议,当这发生在另一段代码中时,我将处理程序设为静态。使处理程序静态也有效。

于 2013-05-01T20:33:55.167 回答
0

BomItemBOMModuleCollection<BomModule> modules),BOMModuleBOMItemCollection<BomItem> items)。Gson 库不喜欢循环引用。从您的课程中删除此循环依赖项。我过去也遇到过与 gson lib 相同的问题。

于 2015-10-27T04:50:26.917 回答
0

当我输入时,我遇到了这个问题:

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

在我的对象中......经过一个小时左右的调试后,这非常有意义!

于 2018-03-12T17:48:33.397 回答
0

避免不必要的变通方法,例如将值设置为 null 或使字段瞬态。正确的做法是使用 @Expose 注释其中一个字段,然后告诉 Gson 仅序列化带有注释的字段:

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

...
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
于 2019-02-12T22:49:36.637 回答
0

我有一个类似的问题,该类有一个 InputStream 变量,我真的不必坚持。因此将其更改为瞬态解决了这个问题。

于 2019-03-24T05:11:43.677 回答
0

经过一段时间与这个问题的斗争,我相信我有一个解决方案。问题在于未解决的双向连接,以及在序列化时如何表示连接。解决该行为的方法是“告诉”gson如何序列化对象。为此,我们使用Adapters.

通过使用Adapters,我们可以知道gson如何序列化Entity类中的每个属性以及要序列化哪些属性。

Foo和是两个与有关系和有关系的Bar实体。我们定义适配器,因此当序列化时,通过从循环引用的角度定义如何序列化是不可能的。FooOneToManyBarBarManyToOneFooBargsonBarFooBar

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...

答案基于个人经验,因此请随时发表评论,证明我错了,纠正错误或扩大答案。无论如何,我希望有人会发现这个答案很有用。

于 2020-03-08T20:58:32.493 回答