jackson-databind: Nested type arguments doesn't work with polymorphic types

When using @JsonSubTypes/@JsonTypeInfo on an interface with a generic parameter (de)serialising a subclass that wraps it’s own generic parameter in another generic class or interface doesn’t work.

It’s a bit complex to explain with words so here’s a short example:

public class GenericPolymorphismTest {
    @JsonTypeInfo(property = "type", use = JsonTypeInfo.Id.NAME)
    @JsonSubTypes({
            @JsonSubTypes.Type(value = AddToList.class, name = "addToList")
    })
    public interface Operation<T> {
        T perform(T operand);
    }

    public static class AddToList<T> implements Operation<List<T>> {
        T value;
        @Override
        public List<T> perform(List<T> operand) {
            operand.add(value);
            return operand;
        }
    }

    public static class Container {
        Operation<List<UUID>> listOperation;
    }

    private static final ObjectMapper objectMapper = new ObjectMapper();
    static {
        objectMapper.setVisibility(new VisibilityChecker.Std(JsonAutoDetect.Visibility.ANY));
    }

    public static void main(String[] args) throws JsonProcessingException {
        AddToList<UUID> addToList = new AddToList<>();
        addToList.value = UUID.randomUUID();
        Container container = new Container();
        container.listOperation = addToList;

        System.out.println(objectMapper.writeValueAsString(container));
    }
}

This crashes with the following exception:

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Class java.util.UUID not subtype of [collection type; class java.util.List, contains [simple type, class java.util.UUID]] (through reference chain: GenericPolymorphismTest$Container["listOperation"]->GenericPolymorphismTest$AddToList["value"])
	at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:388)
	at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:348)
	at com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:343)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:698)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeWithType(BeanSerializerBase.java:581)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:706)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:690)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
	at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:292)
	at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3681)
	at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:3057)
	at GenericPolymorphismTest.main(GenericPolymorphismTest.java:57)
Caused by: java.lang.IllegalArgumentException: Class java.util.UUID not subtype of [collection type; class java.util.List, contains [simple type, class java.util.UUID]]
	at com.fasterxml.jackson.databind.type.TypeFactory.constructSpecializedType(TypeFactory.java:359)
	at com.fasterxml.jackson.databind.cfg.MapperConfig.constructSpecializedType(MapperConfig.java:306)
	at com.fasterxml.jackson.databind.DatabindContext.constructSpecializedType(DatabindContext.java:149)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter._findAndAddDynamic(BeanPropertyWriter.java:870)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:682)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:690)
	... 8 more

If I try work around it by making value into a List<T> deserialisation works but serialisation gives me a List<String> instead of List<UUID> so using a list after perform will throw ClassCastExceptions.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 25 (12 by maintainers)

Commits related to this issue

Most upvoted comments

@dnno Could you please file a new issue for this: even if the problem is related to this one (caused by fix), it is easier to track fix that way. You can add a reference to this issue as likely root cause.

I can’t say much except that this could be related to / same as #1964, which will be fixed for 2.9.6

Yes. So, just to clarify what I meant: existing type resolution works fine, as is, when only resolving a single type. What is needed here is sort of re-parent things (grafting?), and it could either be implemented as separate recursive process (instead of resolving it is actually walking existing type hierarchy, really), or trying to retrofit resolution with this.

There are further challenges in that type objects are (mostly, ideally should be fully) immutable, to figure out what and how to copy.

Yes, I can reproduce this. Looks like code path may be slightly different, but at least there’s something to dig into.