In Java generics are more or less gone at runtime. This is called type erasure. So your HashMap<String, Object> will just be HashMap<?, ?> when your programme runs. So Jackson cannot determine the types Stringand Object. That is why you have to create a TypeReference which will preserve that information at runtime so Jackson can deserialise your JSON String into the correct classes.
If you don't specify a type Jackson will still be able to correctly deserialise simple objects like String or numbers but when a more complex object is encountered it will simply deserialise them into LinkedHashMap which will then most likely lead to a ClassCastException when you try to access the deserialised object.
This TypeReference<T> implements Comparable<TypeReference<T>> is part of jackon library. And this is part of com.fasterxml.jackson.core.type. From the API documentation:
This generic abstract class is used for obtaining full generics type information by sub-classing; it must be converted to ResolvedType implementation (implemented by JavaType from "databind" bundle) to be used. Class is based on ideas from http://gafter.blogspot.com/2006/12/super-type-tokens.html, Additional idea (from a suggestion made in comments of the article) is to require bogus implementation of Comparable (any such generic interface would do, as long as it forces a method with generic type to be implemented). to ensure that a Type argument is indeed given. Usage is by sub-classing: here is one way to instantiate reference to generic type List:
TypeReference ref = new TypeReference<List>() { }; which can be passed to methods that accept TypeReference, or resolved using TypeFactory to obtain ResolvedType.
This is basically used to de-serialize the Generic type of JSON objects, for example the JAVA Collection objects as below:
public List<Double> getDoubleList() {
return getObjectFromJson(new TypeReference<List<Double>>() {
});
}
public <T> T getObjectFromJson(String jsonString, final TypeReference<T> typeReference) {
try {
return new ObjectMapper().readValue(jsonString, typeReference);
} catch (JsonProcessingException e) {
// TODO
}
}
This is a well-known problem with Java type erasure: T is just a type variable, and you must indicate actual class, usually as Class argument. Without such information, best that can be done is to use bounds; and plain T is roughly same as 'T extends Object'. And Jackson will then bind JSON Objects as Maps.
In this case, tester method needs to have access to Class, and you can construct
JavaType type = mapper.getTypeFactory().
constructCollectionType(List.class, Foo.class)
and then
List<Foo> list = mapper.readValue(new File("input.json"), type);
'JavaType' works !!
I was trying to unmarshall (deserialize) a List in json String to ArrayList java Objects and was struggling to find a solution since days.
Below is the code that finally gave me solution.
Code:
JsonMarshallerUnmarshaller<T> {
T targetClass;
public ArrayList<T> unmarshal(String jsonString) {
ObjectMapper mapper = new ObjectMapper();
AnnotationIntrospector introspector = new JacksonAnnotationIntrospector();
mapper.getDeserializationConfig()
.withAnnotationIntrospector(introspector);
mapper.getSerializationConfig()
.withAnnotationIntrospector(introspector);
JavaType type = mapper.getTypeFactory().
constructCollectionType(
ArrayList.class,
targetclass.getClass());
try {
Class c1 = this.targetclass.getClass();
Class c2 = this.targetclass1.getClass();
ArrayList<T> temp = (ArrayList<T>)
mapper.readValue(jsonString, type);
return temp ;
} catch (JsonParseException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null ;
}
}
After constructing JavaType, both call same deserialization functionality, so the only difference is the way generic type is handled.
Second one is fully static, so type must be known in compile type, and can not vary. So it is similar to using basic Class literal.
First one is dynamic, so it can be used to construct things that vary regarding their parameterization.
Personally I prefer first alternative for all cases (it avoids creation of one more anonymous inner classes), but second one may be more readable.
One more way you can achieve this is by :
List<SomeClass> list = Arrays.asList(mapper.readValue(jsonString, SomeClass[].class));