Skip to content

Correctly deserialize forward @JsonIdentityInfo references when using @JsonCreator #3030

@benediktsatalia

Description

@benediktsatalia

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.

Metadata

Metadata

Assignees

Labels

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

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions