Skip to content

Deserializing polymorphic type with defaultImpl sets type ID to null #3313

@joschi

Description

@joschi

Describe the bug

Starting with Jackson Databind 2.13.0, deserializing a polymorphic type with a default implementation (JsonTypeInfo.defaultImpl) and an explicit null for the type ID will produce an instance of the concrete type in which the type ID is null instead of a value provided during construction (in its constructor/@JsonCreator method).

In Jackson Databind 2.12.5 and earlier, this produced an instance of the concrete type in which the type ID field had the value assigned in the constructor/@JsonCreator method.

We're using Jackson Databind to deserialize user-provided input, so unfortunately we cannot prevent them from sending JSON payloads with null in the type ID field, even if it isn't valid in the first place.

Version information

Working on Jackson Databind 2.12.5.
Failing on Jackson Databind 2.13.0.

To Reproduce

class PolymorphicTest {
    @Test
    void deserializingPolymorphicTypeFillsTypeInfoField() throws JsonProcessingException {
        String s = "{\"@type\": null, \"custom\": \"value\"}";

        final BaseClass object = new ObjectMapper().readValue(s, BaseClass.class);

        assertTrue(object instanceof TypeA);
        assertEquals(TypeA.TYPE, object.type);
        assertEquals("value", ((TypeA) object).customA);
    }

    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, defaultImpl = TypeA.class)
    @JsonSubTypes({
            @JsonSubTypes.Type(value = TypeA.class, name = TypeA.TYPE),
            @JsonSubTypes.Type(value = TypeB.class, name = TypeB.TYPE)
    })
    static abstract class BaseClass {
        @JsonTypeId
        @JsonProperty("@type")
        final String type;

        @JsonCreator
        BaseClass(@JsonProperty("@type") String type) {
            this.type = type;
        }
    }

    static class TypeA extends BaseClass {
        static final String TYPE = "A";
        String customA;

        @JsonCreator
        TypeA(@JsonProperty("custom") String custom) {
            super(TYPE);
            this.customA = custom;
        }
    }

    static class TypeB extends BaseClass {
        static final String TYPE = "B";
        String customB;

        @JsonCreator
        TypeB(@JsonProperty("custom") String custom) {
            super(TYPE);
            this.customB = custom;
        }
    }
}

Expected behavior

With Jackson Databind 2.12.5, the unit test succeeds while with Jackson Databind 2.13.0 it fails because object.type is null instead of using the value of the default implementation TypeA ("A"):

org.opentest4j.AssertionFailedError: expected: <A> but was: <null>
	at app//org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:55)
	at app//org.junit.jupiter.api.AssertionUtils.failNotEqual(AssertionUtils.java:62)
	at app//org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
	at app//org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:177)
	at app//org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1141)
	at app//jackson.polymorphic.bug.PolymorphicTest.deserializingPolymorphicTypeFillsTypeInfoField(PolymorphicTest.java:24)

Additional context

I suspect this behavior was changed in the context of #3271 and 32eee1b.

If the new behavior is intentional, it should be mentioned in the release notes for Jackson 2.13.0 as a breaking change.

Metadata

Metadata

Assignees

No one assigned

    Labels

    will-not-fixClosed as either non-issue or something not planned to be worked on

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions