【问题标题】:gson.toJson() throws StackOverflowError in Servletgson.toJson() 在 Servlet 中抛出 StackOverflowError
【发布时间】:2017-09-10 15:52:23
【问题描述】:

我有对象客户列表

List<Client> clientsList=new ArrayList<Client>();
clientsList=clientDao.GetAllClients();

实体客户端有其他列表作为属性:

@ManyToOne(optional=false)
private User createdBy;


@ManyToMany(mappedBy = "Clients")
private Set<ClientType> Types=new HashSet();


@ManyToOne(optional=false)
private LeadSource id_LeadSource;
@ManyToOne(optional=false)
private Agencie id_Agencie;

@OneToMany(cascade=CascadeType.ALL,mappedBy="Owner")
private Set<Propertie> properties=new HashSet();

@OneToMany(cascade=CascadeType.ALL,mappedBy="buyer")
private Set<Sale> sales=new HashSet();

@OneToMany(cascade=CascadeType.ALL,mappedBy = "client")
private Set<Rent> Rents=new HashSet();

@OneToMany(cascade=CascadeType.ALL,mappedBy = "clientDoc")
private Set<Document> Docuements=new HashSet();

当我尝试将客户端列表转换为 json 格式时

out.write(new Gson().toJson(clientsList));

我得到这个错误:

java.lang.StackOverflowError
at com.google.gson.stream.JsonWriter.beforeName(JsonWriter.java:603)
at com.google.gson.stream.JsonWriter.writeDeferredName(JsonWriter.java:401)
at com.google.gson.stream.JsonWriter.value(JsonWriter.java:512)
at com.google.gson.internal.bind.TypeAdapters$8.write(TypeAdapters.java:270)
at com.google.gson.internal.bind.TypeAdapters$8.write(TypeAdapters.java:255)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:113)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:240)

【问题讨论】:

    标签: java angularjs json servlets jakarta-ee


    【解决方案1】:

    这是因为您的实体具有双向连接。例如,Client 有一组Rents,每个租金都有一个对Client 的引用。当您尝试序列化 Client 时,您将序列化其 Rents,然后您必须序列化 Rent 中的每个 Client,依此类推。这就是导致StackOverflowError 的原因。

    要解决这个问题,你必须将一些属性标记为transient(或使用一些类似的注释),例如在Rent 中使用transient Client 然后任何编组库将忽略此属性。

    在 Gson 的情况下,您可以用 @Expose 标记您确实希望包含在 json 中的字段并使用以下方法创建 gson 对象:

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

    附:另外,我想提一下,将您的 JPA 实体转换为 json 并将其发送到某处通常不是一个好主意。我建议创建一个 DTO(数据传输对象)类,其中只包含您需要的信息,最好只使用简单的类型,如 intDateString 等。如果您对这种方法有任何疑问,可以在 Google 上搜索DTOData Transfer Object 或点击此链接:https://www.tutorialspoint.com/design_pattern/transfer_object_pattern.htm

    【讨论】:

    • 非常感谢@Nestor Sokil 的有用回复
    【解决方案2】:

    在解决这个问题一段时间后,我相信我有一个解决方案。 正如@Nestor Sokil 解释的那样,问题在于未解决的双向连接,以及在序列化时如何表示连接。 解决该行为的方法是“告诉”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...

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

    【讨论】:

    • 我序列化你的实体怎么样,我还是遇到同样的问题
    猜你喜欢
    • 2012-04-29
    • 2018-07-10
    • 2015-04-28
    • 2015-10-28
    • 1970-01-01
    • 2013-01-12
    • 2023-03-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多