You need to understand how a TypeReference
works. For that we go into the source code
protected TypeReference()
{
Type superClass = getClass().getGenericSuperclass();
if (superClass instanceof Class<?>) { // sanity check, should never happen
throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information");
}
...
_type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
}
The Class#getGenericSuperclass()
javadoc states
Returns the Type representing the direct superclass of the entity
(class, interface, primitive type or void) represented by this Class.
If the superclass is a parameterized type, the Type object returned
must accurately reflect the actual type parameters used in the source
code.
In other words, if we could do new TypeReference()
(we can't, it's abstract), it would return the Class
instance for the class Object
. However, with anonymous classes (which extend from the type)
new TypeReference<String>(){}
the direct superclass of the instance created is the parameterized type TypeReference
and according to the javadoc we should get a Type
instance that accurately reflect the actual type parameters used in the source code:
TypeReference<String>
from which you can then get the parameterized type with getActualTypeArguments()[0])
, returning String
.
Let's take an example to visualize using anonymous class and using a sub-class
public class Subclass<T> extends TypeReference<AgentResponse<T>>{
public Subclass() {
System.out.println(getClass().getGenericSuperclass());
System.out.println(((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
}
}
Running
new Subclass<String>();
prints
com.fasterxml.jackson.core.type.TypeReference<Test.AgentResponse<T>>
Test.AgentResponse<T>
which fits the javadoc rules. Test.AgentResponse<T>
is the actual parameterized type in the source code. Now, if instead, we had
new Subclass<String>(){}; // anonymous inner class
we get the result
Test.Subclass<java.lang.String>
class java.lang.String
which also fits the bill. The inner class now extends directly from Subclass
which is parameterized with the argument String
in the source code.
You will notice that, with the Subclass
anonymous inner class, we've lost information about the AgentResponse
generic type. This is unavoidable.
Note that
reader = new StringReader("{"element":{"map-element":[{"name":"soto", "value": 123}]}}");
obj = mapper.readValue(reader, new AgentReq<Map<String, Set<Whatever>>>());
will compile and run, but the type AgentReq<Map<String, Set<Whatever>>>
will have been lost. Jackson will use default type to serializes the JSON. The element
will be deserialized as an AgentResponse
, while map-element
will be deserialized as a Map
and the JSON array as an ArrayList
.