JsonTypeInfo seems to be ignored entirely if the serialized value is a list (and not embedded in another bean). This is present with the most recent 2.8.9.
Repro:
public class TypeSerializationTest {
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(name = "sub1", value = Sub1.class),
@JsonSubTypes.Type(name = "sub2", value = Sub2.class)})
public static class Superclass {}
public static class Sub1 extends Superclass {}
public static class Sub2 extends Superclass {}
class ListHolder {
@JsonProperty
ArrayList<Superclass> list;
}
@Test
public void testRoundtripClassName() throws IOException {
ArrayList<Superclass> list = new ArrayList<>(Arrays.asList(
new Sub1(),
new Sub2()));
ListHolder foo = new ListHolder();
foo.list = list;
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(foo));
System.out.println(mapper.writeValueAsString(foo.list));
}
}
Produces this:
{"list":[{"type":"sub1"},{"type":"sub2"}]}
[{},{}]
I'd expect the second line to have the 'type' property information as well?
Ok, this needs to go in FAQ. It gets asked on monthly basis. :)
Yes, this is as expected: the only type that Jackson can see (due to Java Type Erasure) is ArrayList<?>. Since ? is about same as Object, and has no polymorphic type indicators, type is not listed. Same applies to all generic types, but not all Lists (or Maps etc).
There are couple of work-arounds here:
Remove generic-ness by sub-typing, using:
public class MyFooList extends ArrayList
Specifying type information on write call:
mapper.writerFor(new TypeReference>() { })
.writeValueAsString();
both of which work. But in general it may be best to simply avoid use of generic types as root value if possible.
Uh, apologies -- I did scan the bug database, but didn't find it somehow.
I understand why it doesn't work, but still think it's confusing as hell: my assumption was that if the polymorphic tags were specified on the contents of a list, they would be retrieved from each element upon serialization. I see how this can be a challenge on deserialization though (if the typing information isn't globally unique, such as a class name).
@dweiss There are many reports, but since this is not really a bug but feature it's difficult to find issues. I really need to spend time documenting this as FAQ.
As to handling: per-element determination is not made due to a few different reasons
Of these (2) is bigger. Effectively all elements of a container type (Collection, array, Map) must have same base type, configuration of which is used for all elements.
Yup, clear. Thank you and sorry for the noise.
@dweiss No problem at all. I can see how this is surprising and sub-optimal. I just would like to make it easier to figure this out since it is quite intricate. And I may be one of few people who know it fully, having implemented the system... took a while and is partly complicated due to configurability of various aspect.
Most helpful comment
Ok, this needs to go in FAQ. It gets asked on monthly basis. :)
Yes, this is as expected: the only type that Jackson can see (due to Java Type Erasure) is
ArrayList<?>. Since?is about same asObject, and has no polymorphic type indicators, type is not listed. Same applies to all generic types, but not allLists (orMaps etc).There are couple of work-arounds here:
Remove generic-ness by sub-typing, using:
public class MyFooList extends ArrayList { }
Specifying type information on write call:
mapper.writerFor(new TypeReference
>() { })
.writeValueAsString();
both of which work. But in general it may be best to simply avoid use of generic types as root value if possible.