Problem with multi-argument Creator with `@JsonBackReference` property
See original GitHub issueFor the error, please refer to this github example for a consistent repro: https://github.com/atribe/ReproducingBug
The short summary of the issue is that Jackson 2.7.+ - 2.8.6 is not correctly setting up the setter methods for properties defined in my class that I want to deserialize when those classes have managed/back references. Also, another issue is that @JsonProperty and @JsonSetter annotation is ignored, so there isn’t a way to explicitly provide your own setter methods for the properties.
Below is the trace of debugging I have done on this issue.
I have a simple JSON file that shows a ParentObject and a ChildObject. The ParentObject has a managed reference that is a list of its children. The ChildObject has a back reference to its parent object.
In Jackson 2.6.7, when I run the deserializer for these objects, it will properly handle the references and generating mutators for those properties that have dependencies.
In Jackson 2.7.0 - 2.8.6, when I run the deserializer on the same code, it will fail with the error below if I use the managed/back reference annotation. When that managed/back annotation is removed, it will run just fine.
com.fasterxml.jackson.databind.JsonMappingException: Invalid definition for property "" (of type Lcom/atribe/reproducingbug/ChildObject;): No non-constructor mutator available
at [Source: {
"companyName": "My Famke Company",
"companyLogoImageId": "29a8045e-3d10-4121-9f27-429aa74d00ad",
"productId": "ABC-0003",
"productName": "Engineering Test",
"recordNumber": "01",
"revisionNumber": "1.0",
"procedureId": "6e6f607e-fb3f-4750-8a0a-2b38220e3328",
"childSet": [
{
"title": "Child 1",
"componentId": "3f7debe1-cddc-4b66-b7a7-49249e0c9d3e",
"orderLabel": "1",
"orderNumber": 1
}
]
}; line: 1, column: 1]
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:270)
at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:1329)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadPropertyDefinition(DeserializationContext.java:1293)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.constructSettableProperty(BeanDeserializerFactory.java:726)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.addReferenceProperties(BeanDeserializerFactory.java:642)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:230)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:141)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:403)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:349)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)
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.findContextualValueDeserializer(DeserializationContext.java:443)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.createContextual(CollectionDeserializer.java:206)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.createContextual(CollectionDeserializer.java:26)
at com.fasterxml.jackson.databind.DeserializationContext.handleSecondaryContextualization(DeserializationContext.java:681)
at com.fasterxml.jackson.databind.DeserializationContext.findContextualValueDeserializer(DeserializationContext.java:445)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer.findDeserializer(StdDeserializer.java:964)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.resolve(BeanDeserializerBase.java:501)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:293)
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:476)
at com.fasterxml.jackson.databind.ObjectReader._findRootDeserializer(ObjectReader.java:1859)
at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1621)
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1220)
at com.atribe.reproducingbug.ParentObject.deserialize(ParentObject.java:61)
at com.atribe.reproducingbug.ParentObjectTest.deserializeTest(ParentObjectTest.java:19)
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:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
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)
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 com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Process finished with exit code -1
From the stacktrace, I found that when it sets up the managed/back references there is this code below in BeanDeserializerFactory.java. The line of interest is when it calls construct in the SimpleBeanPropertyDefinition
protected void addReferenceProperties(DeserializationContext ctxt,
BeanDescription beanDesc, BeanDeserializerBuilder builder)
throws JsonMappingException
{
// and then back references, not necessarily found as regular properties
Map<String,AnnotatedMember> refs = beanDesc.findBackReferenceProperties();
if (refs != null) {
for (Map.Entry<String, AnnotatedMember> en : refs.entrySet()) {
String name = en.getKey();
AnnotatedMember m = en.getValue();
JavaType type;
if (m instanceof AnnotatedMethod) {
type = ((AnnotatedMethod) m).getParameterType(0);
} else {
type = m.getType();
}
SimpleBeanPropertyDefinition propDef = SimpleBeanPropertyDefinition.construct(
ctxt.getConfig(), m);
builder.addBackReferenceProperty(name, constructSettableProperty(ctxt,
beanDesc, propDef, type));
}
}
}
The method call has this code from SimpleBeanPropertyDefinition.java, where it calls member.getName():
public static SimpleBeanPropertyDefinition construct(MapperConfig<?> config,
AnnotatedMember member) {
return new SimpleBeanPropertyDefinition(member, PropertyName.construct(member.getName()),
(config == null) ? null : config.getAnnotationIntrospector(),
null, EMPTY_INCLUDE);
}
member.getName() returns an empty string every single time (that is the current implementation and has been that way for years). Now after this member is instantiated, there is this call where the AnnotatedMember mutator variable is not correctly created:
protected SettableBeanProperty constructSettableProperty(DeserializationContext ctxt,
BeanDescription beanDesc, BeanPropertyDefinition propDef,
JavaType propType0)
throws JsonMappingException
{
// need to ensure method is callable (for non-public)
AnnotatedMember mutator = propDef.getNonConstructorMutator();
// 08-Sep-2016, tatu: issues like [databind#1342] suggest something fishy
// going on; add sanity checks to try to pin down actual problem...
// Possibly passing creator parameter?
if (mutator == null) {
ctxt.reportBadPropertyDefinition(beanDesc, propDef, "No non-constructor mutator available");
}
.....omitted rest of method for brevity...
If you go to the method propDef.getNonConstructorMutator(), the implementation is below:
@Override
public AnnotatedMember getNonConstructorMutator() {
AnnotatedMember acc = getSetter();
if (acc == null) {
acc = getField();
}
return acc;
}
Go to getSetter() and this is the implementation below. The _member variable is always false and returns null.
@Override
public AnnotatedMethod getSetter() {
if ((_member instanceof AnnotatedMethod)
&& ((AnnotatedMethod) _member).getParameterCount() == 1) {
return (AnnotatedMethod) _member;
}
return null;
}
When we bubble back up to the original caller in constructSettableProperty, it will be null and throw the error even if you explicitly write out a setter method associated with your property that has a managed/back reference.
protected SettableBeanProperty constructSettableProperty(DeserializationContext ctxt,
BeanDescription beanDesc, BeanPropertyDefinition propDef,
JavaType propType0)
throws JsonMappingException
{
// need to ensure method is callable (for non-public)
AnnotatedMember mutator = propDef.getNonConstructorMutator();
// 08-Sep-2016, tatu: issues like [databind#1342] suggest something fishy
// going on; add sanity checks to try to pin down actual problem...
// Possibly passing creator parameter?
if (mutator == null) {
ctxt.reportBadPropertyDefinition(beanDesc, propDef, "No non-constructor mutator available");
}
...omitted rest of method for brevity...
Issue Analytics
- State:
- Created 7 years ago
- Reactions:6
- Comments:27 (14 by maintainers)
Top GitHub Comments
Data point: I think I managed to avoid this bug by adding
suppressConstructorProperties=true
to my lombok@AllArgsConstructor
annotation.Last lombok version v1.16.20 (January 9th, 2018) introduced a breaking change:
So,
suppressConstructorProperties
is not required anymore!But for me it caused another problem: now Jackson cannot deserialize
@Value
classes, generated by lombok, because auto-generated constructor not have@ConstructorProperties
nor@JsonCreator
annotation.So I have to add this
lombok.anyConstructor.addConstructorProperties = true
to thelombok.config
file to restore previous behaviour.