Skip to content

Deserialization fails depending on the order of deserialized objects with "Cannot construct instance (although at least one Creator exists)" #3355

Description

@dinopraso

Describe the bug
Deserialization of a specific class becomes broken if another specific class is deserialized before it. All classes are immutable and depend on @JsonCreator and property-based constructors with all properties annotated correctly with @JsonProperty.

If a class ContainerOne is deserialized before a class ContainerTwo (see below for reference), deserialization will fail, but it you flip their order, it will work as expected.

It appears that some sort of caching is handled incorrectly making the second invocation ignore the property-based creator.

Version information
Reproduced on 2.12.3 and 2.13.0

To Reproduce
Here is the minimal setup I've managed to make to reproduce this.

public class Common {
    private final String property;
    private final List<ContainerTwo> twos;

    @JsonCreator
    public Common(@JsonProperty("property") final String property,
                  @JsonProperty("twos") final List<ContainerTwo> twos) {
        this.property = property;
        this.twos = twos;
    }

    public String getProperty() {
        return property;
    }

    // @JsonIgnoreProperties("common") // This is commented out because it does not contribute to the reproduction, but would be present for such a setup.
    public List<ContainerTwo> getTwos() {
        return twos;
    }
}

public class ContainerOne {
    private final Common common;

    @JsonCreator
    public ContainerOne(@JsonProperty("common") final Common common) {
        this.common = common;
    }

    public Common getCommon() {
        return common;
    }
}

public class ContainerTwo {
    private final Common common;

    @JsonCreator
    public ContainerTwo(@JsonProperty("common") final Common common) {
        this.common = common;
    }

    @JsonIgnoreProperties("twos")
    public Common getCommon() {
        return common;
    }
}

public static void main(final String[] args) throws Exception {
    final String oneJson = "{ \"common\": { \"property\": \"valueOne\" } }";
    final String twoJson = "{ \"common\": { \"property\": \"valueTwo\" } }";

    final ObjectMapper objectMapper = new ObjectMapper();

    final ContainerOne one = objectMapper.readValue(oneJson, ContainerOne.class);
    final ContainerTwo two = objectMapper.readValue(twoJson, ContainerTwo.class); // This fails, unless invoked before the line directly above.
}

I have found two ways to stop this from happening, both of which are not really an option for me at this point:

  1. Remove @JsonIgnoreProperties("twos") from the getter in ContainerTwo
  2. Make Common mutable, with setters

Expected behavior
Since this behavior is specific to the execution order and breaks seemingly unexpectedly, it appears to be a bug.
Expected behavior should be that both objects are deserialized (like they are individually) regardless of their invocation order.

Additional Information
Here is the full stack trace of the exception that happens

Exception in thread "main" com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.dinopraso.serialization.json.Common` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (String)"{ "common": { "property": "valueTwo" } }"; line: 1, column: 15] (through reference chain: com.dinopraso.serialization.json.ContainerTwo["common"])
	at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
	at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1728)
	at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1353)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1415)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:351)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:184)
	at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:542)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:563)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:438)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1405)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:351)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:184)
	at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4675)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3630)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3598)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.2has-failing-testIndicates that there exists a test case (under `failing/`) to reproduce the issue

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions