question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Problem with multi-argument Creator with `@JsonBackReference` property

See original GitHub issue

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

Issue Analytics

  • State:open
  • Created 7 years ago
  • Reactions:6
  • Comments:27 (14 by maintainers)

github_iconTop GitHub Comments

3reactions
joshwandcommented, Mar 31, 2017

Data point: I think I managed to avoid this bug by adding suppressConstructorProperties=true to my lombok @AllArgsConstructor annotation.

1reaction
xak2000commented, Feb 4, 2018

Last lombok version v1.16.20 (January 9th, 2018) introduced a breaking change:

BREAKING CHANGE: lombok config key lombok.anyConstructor.suppressConstructorProperties is now deprecated and defaults to true, that is, by default lombok no longer automatically generates @ConstructorProperties annotations. New config key lombok.anyConstructor.addConstructorProperties now exists; set it to true if you want the old behavior. Oracle more or less broke this annotation with the release of JDK9, necessitating this 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 the lombok.config file to restore previous behaviour.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Multiple back-reference properties with name 'defaultReference'
According to Jackson's javadoc, both @JsonManagedReference and @JsonBackReference accept a name value that binds them together:
Read more >
Jackson - Bidirectional Relationships - Baeldung
First, we'll discuss the Jackson JSON infinite recursion problem. Then we'll see how to serialize entities with bidirectional relationships.
Read more >
Jackson Annotations - @JsonBackReference - Tutorialspoint
Jackson Annotations - @JsonBackReference, @JsonManagedReferences and JsonBackReferences are used to display objects with parent child relationship.
Read more >
JsonBackReference (Jackson-annotations 2.6.0 API)
Annotation used to indicate that associated property is part of two-way ... It is an error for a class to have multiple back...
Read more >
Duplicate proprety ID with Jackson 2.9.2+ - Google Groups
InvalidDefinitionException : Duplicate creator property "id" (index 0 vs 1) ... some option or workaround I could use to get over that duplicated...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found