[英]gson.toJson() throws StackOverflowError in Servlet
我有对象客户列表
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)
那是因为您的实体具有双向连接。 因此,例如Client
有一组Rent
,每个租金都有对Client
的引用。 当您尝试序列化Client
您序列化其Rents
,然后您必须序列化Rent
每个Client
,依此类推。 这就是导致StackOverflowError
原因。
要解决此问题,您必须将某些属性标记为transient
(或使用一些类似的注释),例如在Rent
使用transient Client
然后任何编组库都将忽略此属性。
在GSON的情况下,你可以做标记周围你想包括在使用JSON这些领域的其他方式@Expose
和创建的GSON对象:
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
PS 另外,我想提一下,将您的 JPA 实体转换为 json 并将其发送到某个地方通常不是一个好主意。 我建议创建一个 DTO(数据传输对象)类,您可以在其中仅包含您需要的信息,并且最好只使用简单的类型,如int
、 Date
、 String
等。 如果您对此方法有疑问,您可以谷歌搜索DTO
、 Data Transfer Object
或点击此链接: https : //www.tutorialspoint.com/design_pattern/transfer_object_pattern.htm
经过一段时间与这个问题的斗争,我相信我有一个解决方案。 正如@Nestor Sokil 所解释的,问题在于未解决的双向连接,以及如何在序列化时表示连接。 修复该行为的方法是“告诉” gson
如何序列化对象。 为此,我们使用Adapters
。
通过使用Adapters
我们可以告诉gson
如何序列化Entity
类中的每个属性以及序列化哪些属性。
让Foo
和Bar
是两个实体,其中Foo
具有OneToMany
的关系,以Bar
和Bar
有ManyToOne
关系Foo
。 我们定义了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
...
答案是基于个人经验,因此请随时发表评论,证明我错了,纠正错误,或扩大答案。 无论如何,我希望有人会发现这个答案很有用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.