jackson-databind
jackson-databind copied to clipboard
JsonSubTypes declared on multiple interfaces of a class results in order-dependent resolution and outcome
This is arguably a corner case and (again, arguably) a bug in our code, but I think it'd be nice if jackson could at least return a better message here. TL;DR; version is: if you have an interface extending from more interfaces, each of which declares its own JsonSubTypes, the deserialization can be different and depends on how javac/runtime orders these interfaces in bytecode.
This is a distilled repro:
public class JsonTypeIdTest {
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(name = "c_impl", value = C_Impl.class)
})
private interface A {}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(name = "b_impl", value = B_impl.class),
})
private interface B {}
// Note the order of declarations on inherited interfaces - it makes
// the difference in how types are resolved here.
private interface C extends B, A {}
private static class C_Impl implements C {}
private static class B_impl implements B {}
private static class Clazz {
@JsonProperty public C c;
}
@Test
public void checkSanity() throws IOException {
var mapper = ObjectMapperProvider.createBuilder().build();
mapper.readValue(
"""
{
"c": {
"type": "c_impl"
}
}
""",
Clazz.class);
}
}
The 'c' field on Clazz declares an instance of C. This is an interface that extends from A and B, each of which declare their implementing types. If you declare C to extend B, A, the above fails for me with:
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'c_impl' as a subtype of `com.carrotsearch.lingo4g.internal.util.jackson.JsonTypeIdTest$C`: known type ids = [b_impl] (for POJO property 'c')
at [Source: (String)"{
"c": {
"type": "c_impl"
}
}
"; line: 3, column: 13] (through reference chain: com.carrotsearch.lingo4g.internal.util.jackson.JsonTypeIdTest$Clazz["c"])
so the type info is read from interface B only. But if you reorder the declaration to A, B, things will work just fine as "c_impl" type indeed implements interface C (and B).
I think the ideal way to solve the above would be to collect type mapping information from all superclasses/interfaces, then run a validation check whether type-class mappings are unique, if so - fine. I'm sure it'd break existing code here and there though...