jackson-databind
jackson-databind copied to clipboard
[race condition] Failure to deserialize child object when using `@JsonIgnoreProperties` to break cycle
An issue is being encountered when attempting to deserialize a child object in a parent-child relationship with cyclic references where the @JsonIgnoreProperties
annotation is being used to break the cycle. The problem only occurs if there is an attempt to deserialize a child object directly, before making an attempt to deserialize the parent object directly. This occurs in version 2.8.8, but is also reproducible in version 2.9.0.pr3.
The following test code reproduces the issue:
private static class Parent {
private String name;
@JsonIgnoreProperties("parent")
private List<Child> children;
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) {
if (children == null) {
children = new ArrayList<>();
}
children.add(child);
child.setParent(this);
}
}
private static class Child {
private Parent parent;
private String name;
public Parent getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Test
public void testSerialization() throws Exception {
Parent alice = new Parent();
alice.setName("Alice");
Child bill = new Child();
bill.setName("Bill");
alice.addChild(bill);
ObjectMapper mapper = new ObjectMapper();
System.out.println("Jackson version: " + mapper.version());
String childJson = mapper.writeValueAsString(bill);
String parentJson = mapper.writeValueAsString(alice);
// The following line will fail, but will work if the following
// two lines are reversed.
mapper.readValue(childJson, Child.class);
mapper.readValue(parentJson, Parent.class);
}
This results in the following exception:
com.fasterxml.jackson.databind.JsonMappingException: No _valueDeserializer assigned
at [Source: {"parent":{"name":"Alice","children":[{"name":"Bill"}]},"name":"Bill"}; line: 1, column: 47] (through reference chain: JsonTest$Child["parent"]->JsonTest$Parent["children"]->java.util.ArrayList[0]->JsonTest$Child["name"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:270)
at com.fasterxml.jackson.databind.DeserializationContext.reportMappingException(DeserializationContext.java:1234)
at com.fasterxml.jackson.databind.deser.impl.FailingDeserializer.deserialize(FailingDeserializer.java:27)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:504)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:104)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:276)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:140)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:287)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:259)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:26)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:504)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:104)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:276)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:140)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:504)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:104)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:276)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:140)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3798)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2842)
at JsonTest.testSerialization(JsonTest.java:76)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Hi, I have the same behaviour in version 2.8.10 using spring boot. Is this a known bug? Thanks ticxx
I am surprised that @JsonIgnoreProperties
would have any effect here for List
-valued property -- I would only expect it to work on POJO types (since List
s do not have properties).
It would be nice if it did work (and same for Map
s, arrays), but I don't think it does.
But that leads to question of why/how exception gets thrown...
It looks like I'm experiencing this issue as well. Usually it works as aspected, but sometimes when we deploy our application, this exception is being thrown. Only a reboot of the jvm "fixes" this issue, so I think some initialisation / caching is loaded using the incorrect order.
@cowtowncoder If I understand correctly, @JsonIgnoreProperties
shouldn't be used for this? Maybe @JsonManagedReference
would be a better option here?
@bergvandenp There have been some improvements to thread-safety of class introspection, relatively recently, but I do not remember exact timing. I thought 2.8.10
would have improvements (it's quite recent), but 2.9 did get a cleaned up version. So just in case it was easy enough to test (... which is probably isn't, for rare but nasty bug like this...), I'd try 2.9.3
, if you are running on older version.
If problem occurs on pre-2.8.10 version, I'd definitely try 2.8.11 (latest/last 2.8).
We are experiencing the same issue. Cannot reproduce locally to debug properly, but it occurs quite reliably on our weaker build VMs. Could be some kind of race condition that only happens if the machine is too slow.
Occurs on 2.8.3, 2.8.9, 2.8.11, 2.8.11.1, 2.9.5. Never happens on 2.7.8, 2.7.9.
Workaround: use allowSetters = true
on the @JsonIgnoreProperties
annotations.
@koscejev Thank you for sharing this. Pretty disappointing if it's due to race condition (which sounds plausible based on your description) since quite a bit of work and fixes were made in 2.8 to resolve one specific class of such problems (related to introspection for information accessed via AnnotatedClass
); would have guessed it'd be the opposite wrt 2.7.
FWTW just learned that (as per #1060) use of @JsonIgnoreProperties
does indeed work for POJO-valued arrays, Collection
s. So no mystery there.
Still mystery of why there could/would be race condition.