gson.toJson()抛出StackOverflowError

时间:2012-04-18 13:03:41

标签: java json object gson stack-overflow

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

知道导致此错误的原因吗?我该如何解决?

15 个答案:

答案 0 :(得分:66)

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

在您引用的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;

答案 1 :(得分:24)

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

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

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

答案 2 :(得分:16)

如果你正在使用Realm并且你得到了这个错误,并且给对象带来麻烦的对象扩展了RealmObject,那么在传递之前不要忘记创建一个没有所有Realm绑定的副本realm.copyFromRealm(myObject)通过GSON进行序列化。

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

答案 3 :(得分:11)

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

答案 4 :(得分:6)

我的回答有点迟了,但我认为这个问题还没有很好的解决方案。我最初发现它here

使用Gson,您可以使用@Expose标记想要包含在json中的字段,如下所示:

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

并使用:

创建gson对象
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

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

答案 5 :(得分:2)

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

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

P.S。可能它会工作,并使用@Expose注释在这里检查更多:https://stackoverflow.com/a/7811253/1766166

答案 6 :(得分:1)

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

public MetaInfo(Context context)

当我删除此参数时,错误已经消失。

public MetaInfo()

答案 7 :(得分:1)

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

我创建自己的Json Converter

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

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

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

答案 8 :(得分:0)

在Android中,gson堆栈溢出被证明是Handler的声明。将其移动到未反序列化的类。

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

答案 9 :(得分:0)

BomItem引用BOMModuleCollection<BomModule> modules),BOMModule引用BOMItemCollection<BomItem> items)。 Gson库不喜欢循环引用。从您的类中删除此循环依赖项。我过去也曾用gson lib面对同样的问题。

答案 10 :(得分:0)

我把这个问题发生在我身上:

string input;
string output;

input = "red house";
output = String.Concat(input.Select((currentChar, index) => index == 0 ? Char.ToUpper(currentChar) : currentChar));
//output = "Red house"

在我的对象中...经过一个小时左右的调试后,这是完全合理的!

答案 11 :(得分:0)

对于Android用户,由于对Bundle的自引用导致Bundle,因此无法序列化StackOverflowError

要序列化分发包,register a BundleTypeAdapterFactory

答案 12 :(得分:0)

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

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

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

答案 13 :(得分:0)

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

答案 14 :(得分:0)

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

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

FooBar是两个实体,其中FooOneToMany具有Bar关系,而Bar具有ManyToOne关系到Foo。我们定义了Bar适配器,因此当gson序列化Bar时,通过从Foo循环引用的角度定义如何序列化Bar,将是不可能的。

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触发{{1} } ...

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