[英]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.