jackson-databind icon indicating copy to clipboard operation
jackson-databind copied to clipboard

Deserialization in immutable objects fails depending on the order of deserialized objects with UnresolvedForwardReference

Open jeandemanged opened this issue 2 years ago • 0 comments

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

jeandemanged avatar Jan 26 '22 20:01 jeandemanged