Does Jackson TypeReference work when extended?

后端 未结 1 528
执念已碎
执念已碎 2020-12-07 04:30

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\'

相关标签:
1条回答
  • 2020-12-07 05:16

    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.

    0 讨论(0)
提交回复
热议问题