Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

java jackson parse object containing a generic type object

i have the following problem.

I have to parse a json request into an object that contains a generic type field.

EDIT

i have made some tests using a regular class type (so i make it work before i replace it with generic). Now parsing for a single element works great.

The issue is when i need to parse out a list object out of that class.

So i have to inform jackson somehow that my T is of type list instead of just AlbumModel.

Here is what i have tried.

 @Override
    public ListResponseModel<AlbumModel> parse(String responseBody) throws Exception {
    JavaType type = mapper.getTypeFactory().constructParametricType(ResponseModel.class,
        AlbumModel.class);
    return mapper.readValue(responseBody,
        mapper.getTypeFactory().constructParametricType(ResponseModel.class, type));
    }

But the code above doesn't work. what is the solution for something like this?

my generic type in the ListResponseModel is defined like: List<T> data

succeeded like:

public class BaseResponseModel<T> {

    @JsonProperty("data")
    private T data;

    @JsonProperty("paginations")
    private PaginationModel pagination;
}

so far i have the following code but it always parses into a Hash.

public class ResponseParser extends BaseJacksonMapperResponseParser<ResponseModel<AlbumModel>> {

    public static final String TAG = ResponseParser.class.getSimpleName();

    @Override
    public ResponseModel<AlbumModel> parse(String responseBody) throws Exception {
        return mapper.readValue(responseBody,
                mapper.getTypeFactory().constructParametricType(ResponseModel.class, AlbumModel.class));
    }
}

public abstract class BaseJacksonMapperResponseParser<T> implements HttpResponseParser<T> {

    public static final String TAG = BaseJacksonMapperResponseParser.class.getSimpleName();

    public static ObjectMapper mapper = new ObjectMapper();

    static {
        mapper.disable(Feature.FAIL_ON_UNKNOWN_PROPERTIES);
        mapper.enable(Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
        mapper.configure(SerializationConfig.Feature.WRAP_ROOT_VALUE, true);
    }

}
like image 954
DArkO Avatar asked Nov 23 '25 07:11

DArkO


2 Answers

I agree with eugen's answer but just wanted to expand on it a bit. The first step is to refactor your parse method so it takes a second argument. Instead of allocating the type reference in your method, you require the caller to pass in a TypeReference instance.

public BaseResponseModel<T> parse(String responseBody, TypeReference<T> ref) throws Exception {
    return mapper.readValue(responseBody, ref);
}

Unfortunately your snippet does not show the code which calls parse - so I'll make something up:

BaseResponseParser<Collection<Person>> parser = new BaseResponseParser<Collection<Person>>();
BaseResponseModel<Collection<Person>> result = parser.parse(jsonText, new TypeReference<Collection<Person>>(){});

Notice that when the TypeReference instance is compiled in this case, it a type reference to the real concrete class that we expect.

You could do the same thing passing in a Class at runtime, however TypeReference is a bit more powerful because it even works when type T is a generic collection. There is some magic in the TypeReference implementation that allows it to hold onto type information that would normally be erased.

[update]

Updated to use Collection<Person>. Note - as far as I know as List<Whatever> should work also, but I double checked a project where I was using jackson to deserialize collections. Base class Collection definitely worked so I stayed with that.

like image 146
Guido Simone Avatar answered Nov 24 '25 23:11

Guido Simone


Your type T will be "erased" at runtime, so Jackson does not know what is the real type of T and deserializes it to a Map. You need a second parameter to your parse method that will be Class<T> clazz or TypeReference<T> or java.lang.reflect.Type.

EDIT

Small explanation on the magic of TypeReference. When you do new XX() {} you are creating a anonymous class, so if it is a class with typevariables (parameterized if you prefer), new X<List<Y>>() {}, you will be able to retrieve List<Y> as a java Type at runtime. It is very similar as if you had done :

abstract class MyGenericClass<T> {}
class MySpecializedClass extends MyGenericClass<List<Y>> {}
like image 37
eugen Avatar answered Nov 24 '25 21:11

eugen