Polymorphic subtype deduction behaves differently based on order of fields
See original GitHub issueDescribe the bug I’m using @JsonTypeInfo(use = Id.DEDUCTION) and have created a scenario where the mapper behaves differently depending on how I order my fields, which I think should be undesirable behavior.
Version information 2.12.3
To Reproduce JUnit 5 test:
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.json.JsonMapper;
public class DeductionBasedPolymorphismFieldOrderTest {
@Test
public void test1() throws JsonMappingException, JsonProcessingException {
String json = "{\"kingdom\":\"animal\",\"woof\": \"woof.wav\",\"meow\": \"meow.wav\",\"arf\": \"arf.wav\",\"bark\": \"bark.wav\"}";
Animal animal = new JsonMapper().readValue(json, Animal.class); // Succeeds!
assertTrue(animal.getClass().getName().contains("CatDog"));
}
@Test
public void test2() throws JsonMappingException, JsonProcessingException {
String json = "{\"kingdom\":\"animal\",\"meow\": \"meow.wav\",\"woof\": \"woof.wav\",\"arf\": \"arf.wav\",\"bark\": \"bark.wav\"}";
Animal animal = new JsonMapper().readValue(json, Animal.class);
// Throws: UnrecognizedPropertyException: Unrecognized field "woof" (class
// DeductionBasedPolymorphismFieldOrderTest$Cat)
assertTrue(animal.getClass().getName().contains("CatDog"));
}
@Test
public void test3() throws JsonMappingException, JsonProcessingException {
String json = "{\"kingdom\":\"animal\",\"arf\": \"arf.wav\",\"meow\": \"meow.wav\",\"woof\": \"woof.wav\",\"bark\": \"bark.wav\"}";
Animal animal = new JsonMapper().readValue(json, Animal.class);
// Throws: UnrecognizedPropertyException: Unrecognized field "meow" (class
// DeductionBasedPolymorphismFieldOrderTest$Dog2)
assertTrue(animal.getClass().getName().contains("CatDog"));
}
@JsonTypeInfo(use = Id.DEDUCTION, defaultImpl = CatDog.class)
@JsonSubTypes({ @JsonSubTypes.Type(value = Cat.class), @JsonSubTypes.Type(value = Dog1.class),
@JsonSubTypes.Type(value = Dog2.class) })
public static class Animal {
public String kingdom;
}
public static class Cat extends Animal {
public String meow;
}
public static class Dog1 extends Animal {
public String woof;
public String bark;
}
public static class Dog2 extends Animal {
public String woof;
public String arf;
}
public static class CatDog extends Animal {
public String meow;
public String woof;
public String bark;
public String arf;
}
}
Expected behavior This situation should always trigger the use of defaultImpl as it does in test 1, or, failing that, throw an InvalidTypeIdException referencing “Cannot deduce unique subtype” (but the behavior should be the same across examples).
Additional context The order of fields should not be what decides subtyping, as it is unintuitive and cannot always be controlled for.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:1
- Comments:7 (5 by maintainers)
Top Results From Across the Web
Can Jackson polymorphic deserialization be used to serialize ...
The deduction-based polymorphism feature deduces subtypes based on the presence of properties distinct to a particular subtype.
Read more >Chapter 6 Polymorphism and its limitations - OCaml
It would be unsound to apply this fake_id function to values with different types. The function fake_id is therefore rightfully assigned the type...
Read more >Deduction-Based Polymorphism in Jackson 2.12 - Baeldung
Hence, Jackson, we'll also know how to deduct the polymorphism in this ... We already declared these subclasses as subtypes in the previous ......
Read more >On Understanding Types, Data Abstraction ... - Luca Cardelli
Mechanisms for polymorphism such as overloading, coercion, subtyping, and ... different types (which may not exhibit a common structure) and may behave in....
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
Would it be possible to simply apply
defaultImpl
if deduction could not be performed either for no match or for multiple matches? Or alternatively only for no-match case but not ambiguous ones. I am not sure if there could be differing expectations for the two cases; if not, seems more straightforward (unless detection of problems is in place wheredefaultImpl
not available?). But adding configurability for this would get bit tricky as it seems like a very specific setting (which is bad forXxxFeature
settings).I ran across this today and it also ran contrary to my expectations on how this feature should work.
In my case, I have a REST API with a polymorphic type. In addition to using Jackson to deserialize requests, I also use it to confirm that the requests to the application are valid. I was playing around and passed in a JSON value that overlapped my two types, expecting to get back a deserialization error. Instead, it behaved as described in this ticket. Further, the fact that logically identical syntactically valid JSON was treated differently based on key ordering was also an unexpected surprise.
My expectation going into it was that the mapping would fail when there were multiple possible matching subtypes.