JsonIgnoreProperties1622Test.java
package tools.jackson.databind.tofix;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
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 to reproduce [databind#1622]: Race condition in deserialization with
* {@code @JsonIgnoreProperties} when deserializing child objects before parent
* objects in cyclic references.
*/
public class JsonIgnoreProperties1622Test
extends DatabindTestUtil
{
// Classes for reproducing the issue
static class Parent {
private String name;
@JsonIgnoreProperties("parent")
private List<Child> children = new ArrayList<>();
public Parent() {}
public Parent(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Child> getChildren() {
return children;
}
public void setChildren(List<Child> children) {
this.children = children;
}
public void addChild(Child child) {
children.add(child);
child.setParent(this);
}
}
static class Child {
private String name;
private Parent parent;
public Child() {}
public Child(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Parent getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
}
// Variant with allowSetters workaround
static class ParentWithWorkaround {
private String name;
@JsonIgnoreProperties(value = "parent", allowSetters = true)
private List<ChildForWorkaround> children = new ArrayList<>();
public ParentWithWorkaround() {}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<ChildForWorkaround> getChildren() {
return children;
}
public void setChildren(List<ChildForWorkaround> children) {
this.children = children;
}
}
static class ChildForWorkaround {
private String name;
private ParentWithWorkaround parent;
public ChildForWorkaround() {}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ParentWithWorkaround getParent() {
return parent;
}
public void setParent(ParentWithWorkaround parent) {
this.parent = parent;
}
}
private final ObjectMapper MAPPER = newJsonMapper();
/**
* This test demonstrates the race condition: deserializing a child first
* causes the parent deserialization to fail with "No _valueDeserializer assigned"
*/
@JacksonTestFailureExpected
@Test
public void raceConditionWithChildFirst() throws Exception
{
// First create and serialize the objects
Parent parent = new Parent("Parent1");
Child child = new Child("Child1");
parent.addChild(child);
String parentJson = MAPPER.writeValueAsString(parent);
String childJson = MAPPER.writeValueAsString(child);
// Deserialize child first - this triggers the race condition
Child deserializedChild = MAPPER.readValue(childJson, Child.class);
assertNotNull(deserializedChild);
assertEquals("Child1", deserializedChild.getName());
// Now try to deserialize parent - this fail with the race condition
// Expected error: "No _valueDeserializer assigned"
Parent deserializedParent = MAPPER.readValue(parentJson, Parent.class);
assertNotNull(deserializedParent);
assertEquals("Parent1", deserializedParent.getName());
assertEquals(1, deserializedParent.getChildren().size());
assertEquals("Child1", deserializedParent.getChildren().get(0).getName());
}
/**
* Control test: deserializing parent first works fine
*/
@Test
public void noRaceConditionWithParentFirst() throws Exception
{
Parent parent = new Parent("Parent1");
Child child = new Child("Child1");
parent.addChild(child);
String parentJson = MAPPER.writeValueAsString(parent);
// Deserialize parent first - this should work
Parent deserializedParent = MAPPER.readValue(parentJson, Parent.class);
assertNotNull(deserializedParent);
assertEquals("Parent1", deserializedParent.getName());
assertEquals(1, deserializedParent.getChildren().size());
assertEquals("Child1", deserializedParent.getChildren().get(0).getName());
}
/**
* Test that the workaround with allowSetters = true resolves the issue
*/
@Test
public void workaroundWithAllowSetters() throws Exception
{
ParentWithWorkaround parent = new ParentWithWorkaround();
parent.setName("Parent1");
ChildForWorkaround child = new ChildForWorkaround();
child.setName("Child1");
child.setParent(parent);
parent.setChildren(Arrays.asList(child));
String parentJson = MAPPER.writeValueAsString(parent);
String childJson = MAPPER.writeValueAsString(child);
// Deserialize child first
ChildForWorkaround deserializedChild = MAPPER.readValue(childJson, ChildForWorkaround.class);
assertNotNull(deserializedChild);
assertEquals("Child1", deserializedChild.getName());
// Now deserialize parent - should work with allowSetters workaround
ParentWithWorkaround deserializedParent = MAPPER.readValue(parentJson, ParentWithWorkaround.class);
assertNotNull(deserializedParent);
assertEquals("Parent1", deserializedParent.getName());
assertEquals(1, deserializedParent.getChildren().size());
assertEquals("Child1", deserializedParent.getChildren().get(0).getName());
}
}