I have a parent object and a child object. The parent object may include the same child objects multiple times, so I only serialize the child object once and the next instances are only referenced by their ID. The object deserializes without errors when I remove the @JsonIdentityInfo annotation from the child object. To me this feels like a Jackson bug, but maybe someone spots an error in my code. I made a small example which shows the error (Jackson version used is 2.9.4): The parent class:
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import java.util.ArrayList;
import java.util.Collection;
import lombok.Getter;
import lombok.Setter;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY,
property = "type", defaultImpl = TestClassParent.class)
public class TestClassParent {
@Getter
@Setter
private ITestClassChild child1;
@Getter
@Setter
private Collection<ITestClassChild> children;
public TestClassParent(){
child1 = new TestClassChild();
children = new ArrayList<>();
children.add(child1);
}
}
The child class:
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import lombok.Getter;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY,
property = "type", defaultImpl = TestClassChild.class)
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class,
property="id")
public class TestClassChild implements ITestClassChild{
@Getter
private String id;
public TestClassChild(){
id = "1";
}
}
The interface:
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY,
property = "type", defaultImpl = ITestClassChild.class)
public interface ITestClassChild {
public String getId();
}
The testcase:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import org.junit.Test;
public class TestClassImportExportTest{
@Test
public void test() throws JsonProcessingException, IOException{
ObjectMapper om = new ObjectMapper();
om.registerSubtypes(
TestClassChild.class
);
TestClassParent original = new TestClassParent();
String json = om.writerWithDefaultPrettyPrinter().writeValueAsString(original);
TestClassParent imported = om.readValue(json, TestClassParent.class);
}
}
Executing the test results in the following error:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `json.ITestClassChild` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (String)"{
"type" : "TestClassParent",
"child1" : {
"type" : "TestClassChild",
"id" : "1"
},
"children" : [ "1" ]
}"; line: 7, column: 18] (through reference chain: json.TestClassParent["children"]->java.util.ArrayList[0])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1451)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1027)
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:265)
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedUsingDefaultImpl(AsPropertyTypeDeserializer.java:178)
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:88)
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:254)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:288)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:245)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:27)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:127)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:189)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:161)
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:130)
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:97)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithType(BeanDeserializerBase.java:1171)
at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:68)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2992)
at json.TestClassImportExportTest.test(TestClassImportExportTest.java:18)
As already mentioned above, removing the @JsonIdentityInfo annotation from TestClassChild makes the test case pass without errors. However, I need the annotation in order to have correct imports. The error seems to be caused by the use of collection + interface + id, removing either of these seems to work, I am happy for any help!
So the following workaround should work:
I've added a custom deserializer for each child class (annotating the class with @JsonDeserialize(using = TestClassChildDeserializer.class)) and one for the interface (also annotated the same way). Then I deserialize the object (maybe there is a way to use the default deserializer?) and added the object into a static Hashmap in the Interface deserializer.
The deserializer does nothing more than taking the textValue of the node and returning the value in the HashMap for this node. The child deserializers deserialize the object and save it into the HashMap.
It's very ugly, but works for the moment (although I need to empty the HashMap after deserialization and it's also not threadsafe...).
I also tried the solution proposed here: https://github.com/FasterXML/jackson-databind/issues/1641 to no avail.