jackson-databind
jackson-databind copied to clipboard
Deserialization in immutable objects fails depending on the order of deserialized objects with UnresolvedForwardReference
Describe the bug
When deserializing in immutable objects using @JsonCreator,
the same json (but with different order of attributes) may succeed or fail.
Note: happens when using @JsonIdentityInfo
and @JsonIdentityReference(alwaysAsId = true)
No problem if the objects are mutable.
Not sure if problem is related to #3355 or not.
Maybe just not a supported way of deserializing ?
Version information 2.13.1
To Reproduce
See below two JSONs differing only by attributes order. One can understand that the first one is easier to manage because when deserializing object B the object A is already fully known.
JSON 1
{
"objA": {
"id": "id obj A"
},
"objB": {
"id": "id obj B",
"objA": "id obj A"
}
}
JSON 2 (fails to deser in immutable objects)
{
"objB": {
"id": "id obj B",
"objA": "id obj A"
},
"objA": {
"id": "id obj A"
}
}
Test case with immutable objects (fails)
package com.example.immutablejsonimport;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
class ImmutableJsonImportTests {
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
void test1() throws JsonProcessingException {
final String json1 = "{\"objA\":{\"id\":\"id obj A\"},\"objB\":{\"id\":\"id obj B\",\"objA\":\"id obj A\"}}";
final String json2 = "{\"objB\":{\"id\":\"id obj B\",\"objA\":\"id obj A\"},\"objA\":{\"id\":\"id obj A\"}}";
System.out.println(json1);
Root root1 = objectMapper.readValue(json1, Root.class);
System.out.println(root1.getObjB().getObjA().getId());
System.out.println(json2);
Root root2 = objectMapper.readValue(json2, Root.class); // fails here
System.out.println(root2.getObjB().getObjA().getId());
}
}
class Root {
private final ObjA objA;
private final ObjB objB;
@JsonCreator
public Root(@JsonProperty("objA") ObjA objA,
@JsonProperty("objB") ObjB objB) {
this.objA = objA;
this.objB = objB;
}
public ObjA getObjA() {
return this.objA;
}
public ObjB getObjB() {
return this.objB;
}
}
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope = ObjA.class)
class ObjA {
private final String id;
@JsonCreator
public ObjA(@JsonProperty("id") final String id) {
this.id = id;
}
public String getId() {
return id;
}
}
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope = ObjB.class)
class ObjB {
private final String id;
@JsonIdentityReference(alwaysAsId = true)
private final ObjA objA;
@JsonCreator
public ObjB(@JsonProperty("id") final String id, @JsonProperty("objA") ObjA objA) {
this.id = id;
this.objA = objA;
}
public String getId() {
return this.id;
}
public ObjA getObjA() {
return this.objA;
}
}
stackstrace:
com.fasterxml.jackson.databind.deser.UnresolvedForwardReference: Could not resolve Object Id [id obj A] (for [simple type, class com.example.immutablejsonimport.ObjA]).
at [Source: (String)"{"objB":{"id":"id obj B","objA":"id obj A"},"objA":{"id":"id obj A"}}"; line: 1, column: 43] (through reference chain: com.example.immutablejsonimport.Root["objB"]->com.example.immutablejsonimport.ObjB["objA"])
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectId(BeanDeserializerBase.java:1387)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1480)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:196)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:186)
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.BeanDeserializerBase.deserializeWithObjectId(BeanDeserializerBase.java:1371)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:182)
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:4674)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3629)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3597)
with mutable objects no problem at all (success)
package com.example.immutablejsonimport.aaa;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
class MutableJsonImportTests {
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
void test1() throws JsonProcessingException {
final String json1 = "{\"objA\":{\"id\":\"id obj A\"},\"objB\":{\"id\":\"id obj B\",\"objA\":\"id obj A\"}}";
final String json2 = "{\"objB\":{\"id\":\"id obj B\",\"objA\":\"id obj A\"},\"objA\":{\"id\":\"id obj A\"}}";
System.out.println(json1);
Root root1 = objectMapper.readValue(json1, Root.class);
System.out.println(root1.getObjB().getObjA().getId());
System.out.println(json2);
Root root2 = objectMapper.readValue(json2, Root.class);
System.out.println(root2.getObjB().getObjA().getId());
}
}
class Root {
private ObjA objA;
private ObjB objB;
public void setObjA(ObjA objA) {
this.objA = objA;
}
public void setObjB(ObjB objB) {
this.objB = objB;
}
public ObjA getObjA() {
return this.objA;
}
public ObjB getObjB() {
return this.objB;
}
}
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope = ObjA.class)
class ObjA {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope = ObjB.class)
class ObjB {
private String id;
@JsonIdentityReference(alwaysAsId = true)
private ObjA objA;
public String getId() {
return this.id;
}
public ObjA getObjA() {
return this.objA;
}
public void setId(String id) {
this.id = id;
}
public void setObjA(ObjA objA) {
this.objA = objA;
}
}
Expected behavior Same behavior independently of attributes order. Possibility to deserialize to immutable objects.
Additional context n/a