jackson-databind
jackson-databind copied to clipboard
Correctly deserialize forward `@JsonIdentityInfo` references when using `@JsonCreator`
Is your feature request related to a problem? Please describe.
Currently we cannot use forward references together with a @JsonCreator
. See the following example:
class A1 {
public List<B> bs;
public List<C1> cs;
}
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
class B {
public String id;
}
class C1 {
private B b;
@JsonCreator
public C1(@JsonProperty("b") B b) {
this.b = b;
}
@JsonGetter("b")
public B getB() {
return b;
}
}
class C2 {
public B b;
}
class A2 {
public List<B> bs;
public List<C2> cs;
}
public class Main {
public static void main(final String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
A1 a2 = mapper.readValue("{\"bs\":[{\"id\":\"b1\"},{\"id\":\"b2\"}],\"cs\":[{\"b\":\"b1\"},{\"b\":\"b2\"}]}", A1.class);
System.out.println("Using correct ordered data (no forward references) with JsonCreator works");
A2 a3 = mapper.readValue("{\"cs\":[{\"b\":\"b1\"},{\"b\":\"b2\"}],\"bs\":[{\"id\":\"b1\"},{\"id\":\"b2\"}]}", A2.class);
System.out.println("Using incorrect ordered data (with forward references) with no JsonCreator works");
A1 a4 = mapper.readValue("{\"cs\":[{\"b\":\"b1\"},{\"b\":\"b2\"}],\"bs\":[{\"id\":\"b1\"},{\"id\":\"b2\"}]}", A1.class);
}
}
The first two deserializations work as expected but the third one, where we use forward references and the @JsonCreator
throws an exception.
This is the output when running the above code snippet:
Using correct ordered data (no forward references) with JsonCreator works
Using incorrect ordered data (with forward references) with no JsonCreator works
Exception in thread "main" com.fasterxml.jackson.databind.deser.UnresolvedForwardReference: Could not resolve Object Id [b1] (for [simple type, class B]).
at [Source: (String)"{"cs":[{"b":"b1"},{"b":"b2"}],"bs":[{"id":"b1"},{"id":"b2"}]}"; line: 1, column: 17] (through reference chain: A1["cs"]->java.util.ArrayList[0]->C1["b"])
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectId(BeanDeserializerBase.java:1292)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1381)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:176)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:166)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:542)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:535)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:419)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1310)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:331)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:164)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:285)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:27)
at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:293)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:156)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4482)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3434)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3402)
at Main.main(Main.java:52)
Describe the solution you'd like
Lazily call @JsonCreator
for such objects only after the whole json (and all objects with ids) got read.
Usage example See my example above.
Correct: there may be cases with combination of @JsonCreator
, identity info, and ordered collections (List
s and arrays) that may not be possible support ever, at all. Calling constructor is not possible without having actual object, and conversely values of collections must be deserialized in order.
You may need to change the usage so that List
properties in question are passed by setters or fields; or possibly use Builder-style if immutability is required (builders work as long as built type itself does not use object id; its properties can use them).
Dear Tatu Saloranta (@cowtowncoder),
Just for your information. A related Stack Overflow question that contains a minimal, complete, and verifiable example: java - @JsonIdentityInfo fails to deserialize object cross sections within the same file - Stack Overflow.
Best regards, Sergey Vyacheslavovich Brunov.
Thank you @svbrunov !