Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
248 views
in Technique[技术] by (71.8m points)

java - Does Jackson TypeReference work when extended?

Following snippet is self-explanatory enough. You can see that type information is not erased, but mapper doesn't get the type information. My guess is that jackson doesn't allow this, right ? If I pass TypeReference directly, it is deserialized properly.

public class AgentReq<T> extends TypeReference<AgentResponse<T>> {...}

mapper.readValue(reader, new AgentReq<Map<String, Set<Whatever>>>());

It also doesn't work if I do this :

public class AgentReq<T> {

    public TypeReference<AgentResponse<T>> getTypeRef() {
        return new TypeReference<AgentResponse<T>>() {};
    }
}

mapper.readValue(reader, new AgentReq<Map<String, Set<Whatever>>>()).getTypeRef();

I'm using version 2.1.5.

EDIT: For future reference, do not underestimate the TypeReference constructor when resolving problems. There you can see directly whether it was able to retrieve type information. Btw the answer is NO, you can't extend TypeReference and expect it to work, you can't even override its getType() method and supply it with type information resolved from your class, because all you can get is getClass().getGenericSuperClass() ... You can't do getClass().getGenericClass()

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...