FasterXML / FasterXML/jackson-databind
`@JsonSubTypes` declared on multiple interfaces of a class results in order-dependent resolution and outcome
- Dominant language
- Java
- Stars
- 3.7k
- Forks
- 1.5k
- Avg merge
- 3d 5h
- Merged PRs (30d)
- 30
Description
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...
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the supplied JsonTypeIdTest reproduction and the ObjectMapperProvider.createBuilder().build() entry point, then trace subtype resolution for C and its inherited interfaces. Add a regression test covering both interface declaration orders, and consider the issue done when the behavior is deterministic or reports the conflicting @JsonSubTypes mappings clearly.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100