ObjectIdWithTypeInfo4014Test.java
package tools.jackson.databind.tofix;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import tools.jackson.databind.*;
import tools.jackson.databind.testutil.DatabindTestUtil;
import tools.jackson.databind.testutil.failure.JacksonTestFailureExpected;
import static org.junit.jupiter.api.Assertions.*;
/**
* Test for issue #4014: combining @JsonTypeInfo(include = As.PROPERTY)
* with @JsonIdentityInfo(generator = PropertyGenerator.class) on an interface
* causes deserialization to fail.
*/
public class ObjectIdWithTypeInfo4014Test extends DatabindTestUtil
{
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@c")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "@id")
public interface BaseEntity {
@JsonProperty("@id")
Integer getId();
}
// Classes without setter for @id - demonstrates the bug
static class Foo implements BaseEntity {
private final Integer id;
private Bar bar;
public Foo(Integer id) {
this.id = id;
}
@Override
@JsonProperty("@id")
public Integer getId() {
return id;
}
public Bar getBar() {
return bar;
}
public void setBar(Bar bar) {
this.bar = bar;
}
}
static class Bar implements BaseEntity {
private final Integer id;
private Foo foo;
public Bar(Integer id) {
this.id = id;
}
@Override
@JsonProperty("@id")
public Integer getId() {
return id;
}
public Foo getFoo() {
return foo;
}
public void setFoo(Foo foo) {
this.foo = foo;
}
}
// Classes WITH setter for @id - demonstrates the workaround
static class FooWithSetter implements BaseEntity {
private Integer id;
private BarWithSetter bar;
public FooWithSetter() {
}
public FooWithSetter(Integer id) {
this.id = id;
}
@Override
@JsonProperty("@id")
public Integer getId() {
return id;
}
@JsonProperty("@id")
public void setId(Integer id) {
this.id = id;
}
public BarWithSetter getBar() {
return bar;
}
public void setBar(BarWithSetter bar) {
this.bar = bar;
}
}
static class BarWithSetter implements BaseEntity {
private Integer id;
private FooWithSetter foo;
public BarWithSetter() {
}
public BarWithSetter(Integer id) {
this.id = id;
}
@Override
@JsonProperty("@id")
public Integer getId() {
return id;
}
@JsonProperty("@id")
public void setId(Integer id) {
this.id = id;
}
public FooWithSetter getFoo() {
return foo;
}
public void setFoo(FooWithSetter foo) {
this.foo = foo;
}
}
private final ObjectMapper MAPPER = newJsonMapper();
/**
* Test combining @JsonTypeInfo with @JsonIdentityInfo on an interface.
* Currently fails with "cannot find property with name '@id'" error.
*/
@JacksonTestFailureExpected
@Test
public void testTypeInfoWithIdentityInfoOnInterface() throws Exception
{
// Create circular reference
Foo foo = new Foo(1);
Bar bar = new Bar(2);
foo.setBar(bar);
bar.setFoo(foo);
// Serialization works fine
String json = MAPPER.writeValueAsString(foo);
assertNotNull(json);
// Verify the JSON contains both type info and identity info
assertTrue(json.contains("@c"), "Should contain type information");
assertTrue(json.contains("@id"), "Should contain identity information");
// Deserialization should work but currently fails
BaseEntity result = MAPPER.readValue(json, BaseEntity.class);
assertNotNull(result);
assertInstanceOf(Foo.class, result);
Foo resultFoo = (Foo) result;
assertEquals(1, resultFoo.getId());
assertNotNull(resultFoo.getBar());
assertEquals(2, resultFoo.getBar().getId());
// Verify circular reference is preserved
assertSame(resultFoo, resultFoo.getBar().getFoo());
}
/**
* Test that even with setters for the @id property, deserialization should work.
* Currently still fails when annotations are on the interface.
*/
@JacksonTestFailureExpected
@Test
public void testTypeInfoWithIdentityInfoWithSetter() throws Exception
{
// Create circular reference using classes with setters
FooWithSetter foo = new FooWithSetter(1);
BarWithSetter bar = new BarWithSetter(2);
foo.setBar(bar);
bar.setFoo(foo);
// Serialization works fine
String json = MAPPER.writeValueAsString(foo);
assertNotNull(json);
// Verify the JSON contains both type info and identity info
assertTrue(json.contains("@c"), "Should contain type information");
assertTrue(json.contains("@id"), "Should contain identity information");
// Deserialization should work but currently fails
BaseEntity result = MAPPER.readValue(json, BaseEntity.class);
assertNotNull(result);
assertInstanceOf(FooWithSetter.class, result);
FooWithSetter resultFoo = (FooWithSetter) result;
assertEquals(1, resultFoo.getId());
assertNotNull(resultFoo.getBar());
assertEquals(2, resultFoo.getBar().getId());
// Verify circular reference is preserved
assertSame(resultFoo, resultFoo.getBar().getFoo());
}
}