2

I have list of objects clients

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

Entity Client has others list as attributes:

@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();

and when i try to convert list of clients to json format

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

i get this error :

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)
i.fayza
  • 25
  • 1
  • 1
  • 3

2 Answers2

16

That is because your entities have bidirectional connections. So for example Client has a set of Rents and each rent has a reference to Client. When you try serializing a Client you serialize its Rents and then you have to serialize each Client in Rent and so on. This is what causes the StackOverflowError.

To solve this problem you will have to mark some properties as transient (or use some similar anotation), for example use transient Client in Rent Then any marshalling lib will just ignore this property.

In case of Gson you can do the other way around marking those field you do want to be included in json with @Expose and creating the gson object with:

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

P.S. Also, I would like to mention that converting your JPA entity to json and sending it somewhere is generally not a very good idea. I'd recommend creating a DTO(Data Transfer Object) class where you include only the info you need and ideally using only simple types like int, Date, String and so on. If you have questions about this approach you can google for DTO, Data Transfer Object or follow this link: https://www.tutorialspoint.com/design_pattern/transfer_object_pattern.htm

Nestor Sokil
  • 2,162
  • 12
  • 28
-1

After some time fighting with this issue, I believe i have a solution. As @Nestor Sokil explained problem is in unresolved bidirectional connections, and how to represent connections when they are being serialized. The way to fix that behavior is to "tell" gson how to serialize objects. For that purpose we use Adapters.

By using Adapters we can tell gson how to serialize every property from your Entity class as well as which properties to serialize.

Let Foo and Bar be two entities where Foo has OneToMany relation to Bar and Bar has ManyToOne relation to Foo. We define Bar adapter so when gson serializes Bar, by defining how to serialize Foo from perspective of Bar cyclic referencing will not be possible.

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;
    }
}

Here foo_id is used to represent Foo entity which would be serialized and which would cause our cyclic referencing problem. Now when we use adapter Foo will not be serialized again from Bar only its id will be taken and put in JSON. Now we have Bar adapter and we can use it to serialize Foo. Here is idea:

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;
}

One more thing to clarify, foo_id is not mandatory and it can be skipped. Purpose of adapter in this example is to serialize Bar and by putting foo_id we showed that Bar can trigger ManyToOne without causing Foo to trigger OneToMany again...

Answer is based on personal experience, therefore feel free to comment, to prove me wrong, to fix mistakes, or to expand answer. Anyhow I hope someone will find this answer useful.

quepasa
  • 9
  • 3