Skip to content

Problem with multi-argument Creator with @JsonBackReference property #1516

@sarahlikesglitter

Description

@sarahlikesglitter

For 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...

Metadata

Metadata

Assignees

No one assigned

    Labels

    lombokIssue (likely) related to use of Lombok

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions