[英]GSON.toJson(new RuntimeException()) throws 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
类中的每个属性以及序列化哪些属性。
让Foo
和Bar
是两个实体,其中Foo
具有OneToMany
的关系,以Bar
和Bar
有ManyToOne
关系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.