Deserialization fails depending on the order of deserialized objects with "Cannot construct instance (although at least one Creator exists)"
See original GitHub issueDescribe 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:
- Remove
@JsonIgnoreProperties("twos")
from the getter inContainerTwo
- 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)
Issue Analytics
- State:
- Created 2 years ago
- Comments:8 (4 by maintainers)
Top GitHub Comments
Though the issue still remains to be fixed, since the fact that a set of properties was ignored on one class shouldn’t affect the deserialization of another
The behavior about “unifying annotations” (combining) – that annotations in one accessor are indeed applied to the “whole” logical property, regardless of which accessor had it – is indeed 100% intended and working as defined. The reason for this is to reduce the need to apply multiple duplicate annotations for things like renaming (as illustrated by @yawkat’s example). The process of unification is applied for all Jackson annotations without explicit rules in trying to determine separate semantics for separate annotations. But even if handling were separated, I would argue that
@JsonIgnoreProperties
should apply regardless of setter/getter used to denote it.Having said that it sounds like there is a separate issue which is likely due to the way that
@JsonIgnoreProperties
is applied via reference. It sounds likely that the “modified” instance (with ignoral) gets cached and incorrectly used for non-annotated case. I think there is another existing issue filed for recursive use case that is probably related.