jackson-databind
jackson-databind copied to clipboard
No way to combine `@JsonTypeInfo(include = As.PROPERTY)` and `@JsonIdentityInfo(generator = PropertyGenerator.class)`
Describe the bug
I have tried many different ways to combine @JsonTypeInfo(include = As.PROPERTY)
and @JsonIdentityInfo(generator = PropertyGenerator.class)
, but it always ends up throwing an exception at me like this one:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid Object Id definition for `foo.JacksonDeserializationTest$BaseEntity`: cannot find property with name '@id'
at [Source: (String)"{"@c":"foo.JacksonDeserializationTest$Bar","@id":1,"foo":{"@c":"foo.JacksonDeserializationTest$Foo","@id":0,"other":1}}"; line: 1, column: 1]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1915)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:268)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
at com.fasterxml.jackson.databind.DeserializationContext.findNonContextualValueDeserializer(DeserializationContext.java:644)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.resolve(BeanDeserializerBase.java:539)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:294)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:654)
at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:4956)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4826)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3772)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3740)
Version information 2.15.2
To Reproduce
class JacksonDeserializationTest {
@Test
void test() throws Exception {
Foo foo = new Foo();
Bar bar = new Bar();
foo.setOther(bar);
bar.setFoo(foo);
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(bar));
Bar deserialized = mapper.readValue(mapper.writeValueAsString(bar), Bar.class);
}
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@c")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "@id")
public interface BaseEntity {
class Id {
static int next = 0;
}
@JsonProperty("@id")
Integer getId();
}
public static class Foo implements BaseEntity {
private BaseEntity other;
private Integer id = Id.next++;
@Override
public Integer getId() {
return id;
}
public BaseEntity getOther() {
return other;
}
public void setOther(BaseEntity other) {
this.other = other;
}
}
public static class Bar implements BaseEntity {
private BaseEntity foo;
private Integer id = Id.next++;
@Override
public Integer getId() {
return id;
}
public BaseEntity getFoo() {
return foo;
}
public void setFoo(BaseEntity foo) {
this.foo = foo;
}
}
}
Expected behavior It seems like it should be possible to combine these two features somehow. I have tried all combinations I can think of, but couldn't get it to work, so I assume there must be some bug here.
Additional context
I have a set of Java objects I want to serialize, where the references in the object graph form many cycles, so I need to use @JsonIdentityInfo
for this. Also, I have an inheritance hierarchy, so I need to use @JsonTypeInfo
to cover this.
That does sound like a reasonable use case.
I suspect the issue might be the lack of setter for "@id" -- you could add a bogus @JsonProperty("@id") public void setId() { }
if you don't want assignment?
Thanks for the suggestion. Let me try that and report back.
Meanwhile, I was able to get it to work if I let Jackson generate the IDs, which is also acceptable, even if the ID is redundant.
As an aside: I was also getting strange errors when my classes participating in the cycles didn't have an @JsonCreator
annotated default constructor. Of course it makes sense that the properties corresponding to the edges in the cycle cannot be part of the object constructor used by Jackson, but I had it this way, because when I manually instantiate the object, I pass null
values for these properties and then later update them with a setter. It is just that the error message had me puzzled as to what the problem could be.
I would also like to mention how extremely satisfied I am with Jackson! It has a great feature set, well thought-out APIs, well commented code, is very fast and memory-efficient, and not to forget the excellent community support! Thanks a lot for this huge effort 🙏
I have the same issue when using Constructor-based instantiation (no setters, no mutability). Adding a bogus setter makes it work, but setters should not be part of my interface.