gson.toJson() 抛出 ***Error

Posted

技术标签:

【中文标题】gson.toJson() 抛出 ***Error【英文标题】:gson.toJson() throws ***Error 【发布时间】:2012-04-29 21:33:19 【问题描述】:

我想从我的对象生成一个 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.***Error
    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 内部的某个地方,就会发生这种情况。 异常丢失了根本原因并以JsonWriter.java:473)开始日志,如何识别Gson ***的根本原因 【参考方案1】:

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

在您引用的BomModule 类中:

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

BomModule 的自我引用,显然,GSON 根本不喜欢。

解决方法是将模块设置为null 以避免递归循环。这样我可以避免 ***-Exception。

item.setModules(null);

或者使用transient关键字标记你不想出现在序列化json中的字段,例如:

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

【讨论】:

是的,一个 BomModule 对象可以是另一个 BomModule 对象的一部分。 但这有问题吗? 'Collection modules' 只是一个集合,我认为 gson 应该能够从中制作一个简单的数组? @dooonot:集合中的任何对象是否引用了它们的父对象? 我不确定我是否正确,但是是的。请参阅上面的更新问题。 @dooonot:正如我所怀疑的,它在序列化父集合和子集合时进入了无限循环。你希望它写出什么样的 JSON?【参考方案2】:

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

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

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

【讨论】:

绝对的好收获。 GSON 显然不喜欢对课程的自我引用。省了我很多头疼! +1 另一种解决方法是在字段中添加瞬态修饰符 记录器应该大部分是静态的。否则,您将承担为每个对象创建获取该 Logger 实例的成本,这可能不是您想要的。 (成本不小)【参考方案3】:

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

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

【讨论】:

【参考方案4】:

正如 SLaks 所说,如果您的对象中有循环引用,则会发生 ***Error。

要修复它,您可以为您的对象使用 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();

【讨论】:

【参考方案5】:

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

public MetaInfo(Context context)

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

public MetaInfo()

【讨论】:

我在将服务对象引用作为上下文传递时遇到了这个问题。修复是在使用 gson.toJson(this) 的类中使上下文变量静态。 @user802467 你的意思是安卓服务吗?【参考方案6】:

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

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

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

【讨论】:

【参考方案7】:

BomItem 指的是BOMModule (Collection&lt;BomModule&gt; modules),BOMModule 指的是BOMItem (Collection&lt;BomItem&gt; items)。 Gson 库不喜欢循环引用。从您的课程中删除此循环依赖项。我过去也遇到过与 gson lib 相同的问题。

【讨论】:

【参考方案8】:

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

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

【讨论】:

没错!就我而言,将直接从领域查询的对象列表更改为 ArrayList copyList = new ArrayList(); for(图像图像:图像) copyList.add(realm.copyFromRealm(image)); 使用领域,这正是解决问题的解决方案,谢谢【参考方案9】:

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

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

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

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

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

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

【讨论】:

您知道是否有与此相反的注释吗?我需要忽略 4 个字段,需要包含 30 多个字段。 @jDub9 对不起,我的回答迟了,但我一直在度假。看看this 的回答。希望它能解决你的问题【参考方案10】:

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

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

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

【讨论】:

【参考方案11】:

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

我创建了自己的 Json 转换器

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

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

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

【讨论】:

欢迎提供解决方案链接,但请确保您的答案在没有它的情况下有用:add context around the link 这样您的其他用户就会知道它是什么以及为什么会出现,然后引用最相关的您链接到的页面的一部分,以防目标页面不可用。 Answers that are little more than a link may be deleted. 自我推广 仅仅链接到您自己的图书馆或教程并不是一个好的答案。链接到它,解释它解决问题的原因,提供如何解决问题的代码,并否认你编写了它,以获得更好的答案。见:What signifies “Good” self promotion? 非常感谢。我编辑了我的答案。希望一切都会好起来的:D 与其他评论者所说的类似,您最好在帖子中显示代码中最重要的部分。此外,您无需为回答中的错误道歉。【参考方案12】:

对于 Android 用户,您无法序列化 Bundle,因为对 Bundle 的自引用会导致 ***Error

要序列化一个包,register a BundleTypeAdapterFactory

【讨论】:

【参考方案13】:

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

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

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

【讨论】:

【参考方案14】:

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

【讨论】:

【参考方案15】:

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

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

FooBar 成为两个实体,其中FooBar 具有OneToMany 关系,BarFoo 具有ManyToOne 关系。我们定义了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...

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

【讨论】:

为句柄序列化过程本身定义适配器是处理循环依赖的另一种方法。尽管有其他注释可以防止发生这种情况而不是编写适配器,但您可以选择它。

以上是关于gson.toJson() 抛出 ***Error的主要内容,如果未能解决你的问题,请参考以下文章

Gson.toJson返回null,ProGuard问题

Gson toJson(),奇怪的行为(产生空的 json)

Gson toJson 返回 JSON 字符串而不是 JSON 对象

Konlin中的Gson toJson和ArrayList

Gson地图 反对

javaWeb笔记:gson