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 stackoverflow的根本原因
Siddharth

Answers:


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

但这是一个问题吗?“ Collection <BomModule>模块”只是一个集合,我认为gson应该能够从中制作出一个简单的数组?
nimrod

@dooonot:集合中的任何对象都引用其父对象吗?
SLaks 2012年

我不确定我是否正确,但是可以。请参阅上面的更新问题。
nimrod 2012年

@dooonot:正如我所怀疑的,当序列化父集合和子集合时,它会陷入无限循环。您希望它编写哪种JSON?
SLaks

29

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

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

这可以通过制作记录器static或简单地将其移至实际功能中来解决。


4
绝对很棒。显然,GSON根本不喜欢那种对班级的自我指责。救了我很多头疼!+1
克里斯托弗

1
解决此问题的另一种方法是在字段中添加瞬态修改器
gawi

记录器大部分应该是静态的。否则,您将招致每次创建对象都需要获取Logger实例的费用,这可能不是您想要的。(费用不小)
stolsvik

26

如果您使用的是Realm,但遇到此错误,并且麻烦的对象扩展了RealmObject,请不要忘记在不realm.copyFromRealm(myObject)传递所有Realm绑定的情况下创建副本,然后再传递给GSON进行序列化。

我错过了仅在复制的一堆对象中执行此操作的过程……我花了很长时间才意识到,因为堆栈跟踪未命名对象的类/类型。问题是,此问题是由循环引用引起的,但它是RealmObject基类中某个地方的循环引用,而不是您自己的子类,因此很难发现!


1
没错!在我的情况下,将我的对象列表直接从领域查询到ArrayList <Image> copyList = new ArrayList <>(); for(Image image:images){copyList.add(realm.copyFromRealm(image)); }
里卡多·穆蒂

使用领域,这正是解决问题的解决方案,谢谢
Jude Fernandes

13

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

9

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

随着GSON你可以标记你的字段希望被包括在使用JSON@Expose是这样的:

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

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

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

您只是公开的循环引用。那帮了我大忙!


您是否知道是否有与之相反的注释?有4个字段需要忽略,而30多个字段需要包含。
jDub9

@ jDub9对不起,我的回答很晚,但我一直在休假。看看这个答案。希望它能解决您的问题
ffonz

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 '16

1

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

我创建自己的Json Converter

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

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

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


2
欢迎使用指向解决方案的链接,但是请确保没有该链接的情况下您的回答是有用的:在该链接周围添加上下文,以便您的其他用户可以了解它的含义和含义,然后引用您所使用页面中最相关的部分如果目标页面不可用,请重新链接。只是链接的答案可能会被删除。
Paul Roub '18

2
自我推广仅仅链接到您自己的库或教程不是一个好的答案。链接到该页面,说明其解决问题的原因,提供有关如何解决此问题的代码,并否认您编写了此代码,这是一个更好的答案。请参阅: 什么表示“良好”的自我提升?
Shree '18

非常感谢。我已经编辑了答案。希望这将是罚款:d
陈南忠

与其他评论者所说的类似,最好在您的文章中显示代码中最重要的部分。另外,您不必为答案中的错误而道歉。
0xCursor

0

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

根据Zar的建议,当代码另一部分中发生这种情况时,我将处理程序设为静态。使处理程序静态化也可以。


0

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


0

当我放置以下内容时,我遇到了这个问题:

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

以我的目标...经过一个小时左右的调试,这非常有意义!



0

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

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

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

0

我有一个类似的问题,其中该类具有一个InputStream变量,而我并没有真正保留它。因此,将其更改为Transient可以解决此问题。


0

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

通过使用Adapters我们可以告诉您gson如何序列化Entity类中的每个属性以及序列化哪些属性。

FooBar是两个实体,其中FooOneToMany关系BarBarManyToOne关系Foo。我们定义了Bar适配器,因此在进行gson序列化时BarFooBar循环引用的角度定义如何进行序列化将是不可能的。

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

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


为处理程序序列化程序本身定义适配器是处理循环依赖性的另一种方法。您可以选择它,尽管还有其他注释可以防止发生这种情况,而无需编写适配器。
Sariq Shaikh
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.