0

I have an http endpoint that returns different type of json say Car, Bike, Truck. The structure of JSON is following, It is very much similar for each class

{
    "Rows": [
        {
            "key1": "val1",
            "key2": "val2",
        }
    ]
}

I want to write a generic method that creates the list object (Car, Bike, Truck) based on type parameter, (I have all pojo created with proper jackson bindings and they extend Vehicle class)

I came up with following but it is throwing exception at runtime

static <T extends Vehicle> List<T> getTiles(String vehicleName, Class<T> concreteClass) throws IOException {
    Map<String, String> queryParamMap = new HashMap<>();
    queryParamMap.put("apiKey", "<KEY>");
    queryParamMap.put("type", vehicleName);
    

    ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

    OkHttpClient client = new OkHttpClient();
    HttpUrl.Builder builder = new HttpUrl.Builder()
            .scheme("https")
            .host("<URL>")
            .addPathSegment("1");
            

    queryParamMap.entrySet().stream().forEach(k -> builder.addQueryParameter(k.getKey(), k.getValue()));
    HttpUrl url = builder.build();

    Request request = new Request.Builder()
            .addHeader("accept", "application/json")
            .url(url).build();

    Response response = client.newCall(request).execute();
    Map<String, List<T>> m = mapper.readValue(response.body().string(),
            new TypeReference<Map<String, List<T>>>() {
            });

    return m.get("Rows");
}

And I am calling it like

List<Car> cars = Utils.getTiles("CAR", Car.class);

I get the following exception

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.models.Vehicle` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information

I know that this is related to type-erasure, as at runtime T gets bound to Vehicle not Car.

How do I solve this ?

Ishan Bhatt
  • 9,287
  • 6
  • 23
  • 44
  • Are you using [JsonTypeInfo](https://fasterxml.github.io/jackson-annotations/javadoc/2.4/com/fasterxml/jackson/annotation/JsonTypeInfo.html) to deserialize the vehicle data? – Mustafa Jul 14 '21 at 07:50
  • 1
    Try to create `MapType` using type factory like in this question: [Deserializing into a HashMap of custom objects with jackson](https://stackoverflow.com/questions/18002132/deserializing-into-a-hashmap-of-custom-objects-with-jackson). In your example it should be: `typeFactory.constructMapType(HashMap.class, String.class, concreteClass);` and use it instead of `new TypeReference>>() {}` – Michał Ziober Jul 14 '21 at 08:09
  • @MichałZiober, The one you suggested works but I lose the type information. As I need to have a list as value , And if I pass typeFactory.constructMapType(HashMap.class, String.class, List); it is not the correct object anymore. And I can not pass List. – Ishan Bhatt Jul 14 '21 at 08:42
  • 1
    Sorry, I did not notice the `List` type there. In this case you need to create list type and put as an argument to map. Take a look at this question: [How to deserialize generic List with Jackson?](https://stackoverflow.com/questions/61150873/how-to-deserialize-generic-listt-with-jackson/61154659#61154659). Example: `listType = constructCollectionType(List.class, concreteClass)` and use it later: `typeFactory.constructMapType(HashMap.class, String.class, listType)` – Michał Ziober Jul 14 '21 at 09:26
  • @MichałZiober typeFactory.constructMapType(HashMap.class, String.class, listType) does not take listType as a parameter. It's a compilation error. – Ishan Bhatt Jul 14 '21 at 09:48
  • I got it, Took reference from https://stackoverflow.com/questions/27523186/how-to-convert-a-json-string-to-a-mapstring-setstring-with-jackson-json , I needed to pass StringType rather than String.class and it did the trick – Ishan Bhatt Jul 14 '21 at 10:05
  • You can, you need to convert `String` class too. If you have this static import: `com.fasterxml.jackson.databind.type.TypeFactory.defaultInstance` you can write: `defaultInstance().constructMapLikeType(HashMap.class, defaultInstance().constructType(String.class), listOf(clazz))` where `listOf` method comes from previous comment. Does it work for you now? Did it fixed your problem? – Michał Ziober Jul 14 '21 at 10:06

0 Answers0