gson.toJson()在Servlet中抛出StackOverflowError

时间:2017-04-14 11:29:35

标签: java angularjs json servlets java-ee

我有对象客户列表

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)

2 个答案:

答案 0 :(得分:11)

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

的原因

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

在Gson的情况下,你可以用另一种方式来标记那些想要包含在@Expose的json中并使用以下内容创建gson对象的字段:

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

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

答案 1 :(得分:0)

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

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