question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Polymorphic subtype deduction behaves differently based on order of fields

See original GitHub issue

Describe 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:open
  • Created 2 years ago
  • Reactions:1
  • Comments:7 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
cowtowncodercommented, Jun 25, 2021

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 where defaultImpl not available?). But adding configurability for this would get bit tricky as it seems like a very specific setting (which is bad for XxxFeature settings).

0reactions
mjustincommented, Oct 10, 2022

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.

Read more comments on GitHub >

github_iconTop 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 >
Bountysource
Polymorphic subtype deduction behaves differently based on order of fields.
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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found