zonzon
zonzon

Reputation: 183

Jackson - Lazy List initialization error when deserializing a ManyToOne relation

I have 2 simple classes:

Cat has nothing particular, and CatStore has a List<Cat>, the relation mapped as:

In Cat class:

@ManyToOne
private CatStore catstore;

In CatStore:

@OneToMany(mappedBy="catstore", fetch=FetchType.EAGER)
private List<Cat> cats;

The relation seems to work OK, as Jackson can generate me a correct JSON when I run

String json = mapper.writeValueAsString(catStore);

But if I try to deserialize the JSON I just generated, with this line:

CatStore c2 = mapper.readValue(json, CatStore.class);

I get a JsonProcessingException saying

failed to lazily initialize a collection, could not initialize proxy - no Session (through reference chain: fr.truc.java.cleanspringbootMVC.model.CatStore["cats"]

The deserialization actually works if there is no cats in the CatStore like with the following JSON:

{
  "id":123,
  "cats":["org.hibernate.collection.internal.PersistentBag",[]],
  "name":"NO cats"
}

But this one triggers the error:

{
    "id": 121,
    "cats": ["org.hibernate.collection.internal.PersistentBag", [
        ["fr.truc.java.cleanspringbootMVC.model.Cat", {
            "id": 118,
            "name": "luc",
            "nbPattes": 4
        }],
        ["fr.truc.java.cleanspringbootMVC.model.Cat", {
            "id": 119,
            "name": "andré",
            "nbPattes": 4
        }],
        ["fr.truc.java.cleanspringbootMVC.model.Cat", {
            "id": 120,
            "name": "cheval",
            "nbPattes": 4
        }]
    ]],
    "name": "Cat en kit"
}

I really do not know why a lazy loading exception would trigger here as the data is contained in the JSON string.

Is this related to the PersistentBag?

Is this because I do not use a custom serializer/deserializer?

Upvotes: 0

Views: 327

Answers (3)

Marries
Marries

Reputation: 1

I ran into exactly the same issue. To fix it while using enableDefaultTyping (or the currently recommended activateDefaultTyping) I had to use Hibernate5Module: (Note that the required module depends on your hibernate version and has to be added as an additional dependency.)

mapper.registerModule(new Hibernate5Module()
   .configure(Hibernate5Module.Feature.REPLACE_PERSISTENT_COLLECTIONS, true));

This replaces the PersistentBag in your output with an ArrayList and prevents the deserialization error.

However this feature does not seem to work with FetchType.EAGER for a reason unknown to me. Fortunately @Fetch(FetchMode.JOIN) does the trick.

Another thing to take into account is that the REPLACE_PERSISTENT_COLLECTIONS feature generated malformed Json for me (wrapping the list content in double square brackets). But this was resolved by using lenient deserialization.

Upvotes: 0

GeertPt
GeertPt

Reputation: 17874

If you use JPA, all your entities will be enhanced by bytecode to allow lazy loading and such. They are not really suitable for serialization, except by hibernate, who is aware of it. But Jackson doesn't know about it, so it will screw up the enhanced bytecode.

It's also not a good idea to use the same class for JPA mapping and for Jackson mapping. The JPA classes have fields that map to columns and such, whereas the classes for Jackson map to a javascript schema. If you use them in the same class, you can never change your database schema without affecting your JSON schema and possible breaking backwards compatibility with whoever is using the JSON.

It's better to have separate classes (DTO's) to use for Jackson, and do some mapping between them. Or use mapstruct to generate a mapper automatically.

Upvotes: 1

zonzon
zonzon

Reputation: 183

The error seems to be related to mapper.enableDefaultTyping();

When enabled, the typing of each class is outputted in the JSON. (which I wanted)

For some reason, it causes the above error on a @ManyToOne

When disabled, PersistentBag no longer appears in my serialized JSON, and the deserializing works...

Upvotes: 0

Related Questions