Search code examples
javaspringgeneric-collections

How to pass generic class as a generic parameter of method


I have a problem with passing class as a generic param of the method, f.e. I have a simple method:

<T> T sendRequest(SomeRestApiRequest request, Class<T> responseClass)

which parsing response to specified form. I use them in this way:

ItemListJSON itemList = new ItemListJSON();
itemList = someRestClient.sendRequest(req, ItemListJSON.class);

for ItemListJSON.class which look like that:

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({"totalSize","items"})
public class ItemListJSON {

    @JsonProperty("items")
    private List<SalonJSON> items;

    @JsonProperty("totalSize")
    private int totalSize;

    //...getters, setters...
}

and everything's fine. But my question is:

Is it possible to pass generic class as an argument of sendRequest method?

I want that ItemListJSON class is generic, in my case:

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({"totalSize","items"})
public class ItemListJSON<T> {

    @JsonProperty("items")
    private List<T> items;

    @JsonProperty("totalSize")
    private int totalSize;

    //...getters, setters...
}

But when I was trying use sendRequest method in this way:

ItemListJSON<SalonJSON> itemList = new ItemListJSON<SalonJSON>();
itemList = someRestClient.sendRequest(req, ItemListJSON.class);

I got warning on Eclipse IDE

Type safety: The expression of type ItemListJSON needs unchecked conversion to conform to ItemListJSON

and when method was called i got error in server console:

SEVERE: Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to xxx.ServiceCategoryJSON] with root cause
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to xxx.ServiceCategoryJSON

@EDIT:

I debbuged my sendRequest method and I found that error occurs in processResponse method, where is mapping response to object by ObjectMapper.

private <T> T processResponse(Response response, Class<T> responseClass) throws ParseException, IOException {
        ObjectMapper om = new ObjectMapper();
        om.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);     
        return om.readValue(response.getBody(), responseClass); //throw exception
    }

Solution

  • You can do this that way by passing com.fasterxml.jackson.core.type.TypeReference instead of Class<T>

    public class GenericSerializationTest {
    
        @Data //lombok
        public static class ItemListJSON<T> {
            private List<T> items;
        }
    
        @Data //lombok
        public static class StructureExample {
            private final String name;
            private final Double price;
        }
    
        public static class Sender {
            private final ObjectMapper objectMapper = new ObjectMapper();
    
            public <T> T sendRequest(String json, TypeReference typeReference) throws IOException {
                //sender logic - in this case I assume that json is API response
                return objectMapper.readValue(json, typeReference);
            }
        }
    
        @Test
        public void testMethod() throws IOException {
            Sender sender = new Sender();
            ItemListJSON<StructureExample> test = sender.sendRequest("{\"items\": [{\"name\":\"MacBook Pro\",\"price\":101.345}, {\"name\":\"MacMini\",\"price\":102.345}]}", new TypeReference<ItemListJSON<StructureExample>>() {});
    
            assertEquals("Should contain only 2 items", 2, test.getItems().size());
            assertEquals("Name of first item is not correct", "MacBook Pro", test.getItems().get(0).getName());
            assertEquals("Name of second item is not correct", "MacMini", test.getItems().get(1).getName());
        }
    }