Gianx
Gianx

Reputation: 61

JSON deserializer with generic type using TypeReference

I'm trying to generalise this method:

public EventStream<Greeting> deserialize(String value){
    ObjectMapper mapper = new ObjectMapper();

    EventStream<Greeting> data = null;
    try {
        data = new ObjectMapper().readValue(value, new TypeReference<EventStream<Greeting>>() {});
    } catch (IOException e) {
        e.printStackTrace();
    }
    return data;
}

where EventStream is:

public class EventStream<T> {

    private EventHeaders headers;

    @JsonDeserialize
    private T payload;
}

What I'd like to have is replace the specific Object Greeting with a generic, in the deserialize method.

I tried with this:

public <T> EventStream<T> deserialize(String value){
    ObjectMapper mapper = new ObjectMapper();

    EventStream<T> data = null;
    try {
        data = new ObjectMapper().readValue(value, new TypeReference<EventStream<T>>() {});
    } catch (IOException e) {
        e.printStackTrace();
    }
    return data;
}

But the payload inside the EventStream result is deserialized as LinkedHashMap. It seems like TypeReference ignored the generic type. Any idea? Thanks

Upvotes: 4

Views: 2070

Answers (1)

Patric
Patric

Reputation: 1627

What you encountered here is a common problem caused by something called type erasure, the way java implements generics.

Type erasure can be explained as the process of enforcing type constraints only at compile time and discarding the element type information at runtime. [1]

So at the time you try to deserialize your object, the type T is not known and it is just treated as Object and the deserialization result will default default to Map (LinkedHashMap to be precise).

You could make your method generic by passing your targetClass as an additional argument to the function call like so:

public <T> EventStream<T> deserialize(String value, Class<T> targetClass)

Then you use the TypeFactory of your mapper to create a type of this targetClass

JavaType type = mapper.getTypeFactory().constructParametricType(EventStream.class, targetClass);

which you can pass to the readValue method:

data = mapper.readValue(value, type);

Complete code:

public <T> EventStream<T> deserialize(String value, Class<T> targetClass){
    ObjectMapper mapper = new ObjectMapper();
    JavaType type = mapper.getTypeFactory()
        .constructParametricType(EventStream.class, targetClass);
    try {
        return mapper.readValue(value, type);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

[1] https://www.baeldung.com/java-type-erasure

Upvotes: 1

Related Questions