I've been using Jackson to serialize/deserialize objects for years and have always found it needlessly complicated to use TypeReference<T>
to deserialize List
etc. I created a simple helper function:
public static <T> TypeReference<List<T>> list() {
return new TypeReference<List<T>>(){}
}
With intended use:
List<Foo> foos = objectMapper.readValue(json, list());
And it works! Kind of. When inspecting through the debugger, rather than a list of Foo
, it is rather a list of LinkedHashMap
. I understand that ObjectMapper
deserializes into LinkedHashMap
for type Object
and I read the explanation for that here:
Jackson and generic type reference
However, why is it able to assign List<LinkedHasMap>
to a List<Foo>
? At the very least shouldn't that be some sort of ClassCastException
?
Also, is there anyway to do this with Java's type system?
NOTE: the following method declaration has the same issue, which makes sense because the additional argument is not needed for T
to be determined:
public static <T> TypeReference<List<T>> listOf(Class<T> ignored) {
return new TypeReference<List<T>>(){}
}
It works like this because of type erasure in Java. Please, read about it before you start reading next part of this answer:
As you probably know right now, after reading above articles, your method after compilation looks like this:
static <T> TypeReference<List> listOf(Class<T> ignored) {
return new TypeReference<List>(){};
}
Jackson will try to find out the most appropriate type for it which will be java.util.LinkedHashMap
for a JSON object. To create an irrefutable type, you need to use com.fasterxml.jackson.databind.type.TypeFactory
class. See below example:
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import java.io.File;
import java.util.List;
public class JsonTypeApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
System.out.println("Try with 'TypeFactory'");
List<Id> ids = mapper.readValue(jsonFile, CollectionsTypeFactory.listOf(Id.class));
System.out.println(ids);
Id id1 = ids.get(0);
System.out.println(id1);
System.out.println("Try with 'TypeReference<List<T>>'");
List<Id> maps = mapper.readValue(jsonFile, CollectionsTypeFactory.erasedListOf(Id.class));
System.out.println(maps);
Id maps1 = maps.get(0);
System.out.println(maps1);
}
}
class CollectionsTypeFactory {
static JavaType listOf(Class clazz) {
return TypeFactory.defaultInstance().constructCollectionType(List.class, clazz);
}
static <T> TypeReference<List> erasedListOf(Class<T> ignored) {
return new TypeReference<List>(){};
}
}
class Id {
private int id;
// getters, setters, toString
}
Above example, for below JSON
payload:
[
{
"id": 1
},
{
"id": 22
},
{
"id": 333
}
]
prints:
Try with 'TypeFactory'
[{1}, {22}, {333}]
{1}
Try with 'TypeReference<List<T>>'
[{id=1}, {id=22}, {id=333}]
Exception in thread "main" java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.example.Id
at com.example.JsonTypeApp.main(JsonTypeApp.java:27)
See also: