How to convert JSON into generic Java classes using Jackson (avoiding error LinkedHashMap cannot be cast to ...)

  • |
  • 19 April 2023
Post image

Jackson is a popular Java library for working with JSON. Even it is easy to use, when converting a JSON string to a generic Java class, such as ApiGenericResponse<T>, you may run into an issue where the response object is of type LinkedHashMap instead of the expected type T.

Problem

Let’s say you have the following classes in your Java project:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ApiGenericResponse<T> {
    private T response;
    private boolean success;
    private int httpStatusCode;
}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DummyResponse {
    private String name;
    private String surname;
}

And you have the following static method to convert response to your ApiGenericResponse:

public class App {
    private static final ObjectMapper objectMapper = new ObjectMapper();

    public static <T> ApiGenericResponse<T> readJsonAs(String json) throws Exception{
        return objectMapper.readValue(json, new TypeReference<ApiGenericResponse<T>>() {});
    }

    public static void main( String[] args ) {
        String json = "{\"response\":{\"name\":\"test\",\"surname\":\"test\"},\"success\":true,\"httpStatusCode\":200,\"success\":true}";
        ApiGenericResponse<DummyResponse> response = readJsonAs(json);
        System.out.println(response.getResponse().getName());
    }
}

If you run this method, you will get a ClassCastException with the error message: class java.util.LinkedHashMap cannot be cast to class org.example.DummyResponse. This is because Jackson can’t determine the exact object inside ApiGenericResponse and returns a LinkedHashMap instead.

Exception in thread "main" java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class org.example.DummyResponse (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; org.example.DummyResponse is in unnamed module of loader 'app')
	at org.example.App.main(App.java:31)

You can easily solve this problem by modifying the readJsonAs:

Solution

public class App {
    private static final ObjectMapper objectMapper = new ObjectMapper();

    public static  <T> ApiGenericResponse<T> readJsonAs(String json, Class<T> responseType) throws Exception {
        // Create a TypeReference object that represents the type ApiGenericResponse<T>
        TypeReference<ApiGenericResponse<T>> typeReference = new TypeReference<ApiGenericResponse<T>>() {};

        // Deserialize the JSON string into an instance of ApiGenericResponse<T>
        ApiGenericResponse<T> apiResponse = objectMapper.readValue(json, typeReference);

        // Get the actual response object from the response field, and convert it to the specified type
        T response = objectMapper.convertValue(apiResponse.getResponse(), responseType);

        // Set the actual response object to the response field
        apiResponse.setResponse(response);

        return apiResponse;
    }

    public static void main( String[] args ) throws Exception {

        DummyResponse dummyResponse = DummyResponse.builder().name("test").surname("test").build();
        ApiGenericResponse<DummyResponse> test = ApiGenericResponse.<DummyResponse>builder()
                .httpStatusCode(200)
                .success(true)
                .response(dummyResponse)
                .build();
        System.out.println(objectMapper.writeValueAsString(test));

        String json = "{\"response\":{\"name\":\"test\",\"surname\":\"test\"},\"success\":true,\"httpStatusCode\":200,\"success\":true}";
        ApiGenericResponse<DummyResponse> response = readJsonAs(json, DummyResponse.class);
        System.out.println(response.getResponse().getName());
    }
}

The only difference is that we are providing information about the expected type into Jackson. I believe comments are self-explanatory.

You May Also Like