jackson-databind: `@JsonInject` fails on trying to find deserializer even if inject-only

@JsonInject tries to “process” the injected class for deserialization even though it is not deserialized. As a result, you may get errors if the class is not suitable for deserialization, e.g., two getters with the same name but different signature. Jackson 2.6.1.

The following code fails as is - stack trace below - but works if you comment out one of the setA methods on class InjectMe. (Uses TestNG, AssertJ.)

(I believe it is probably a common use case that you might use @JsonInject to inject instances of classes from your application environment that you never expect to serialize/deserialize. They may very well have multiple setters with the same name but different signature. ObjectMapper itself is like this: that’s how I discovered this issue.)

import java.io.IOException;

import org.testng.annotations.Test;

import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.InjectableValues;
import com.fasterxml.jackson.databind.ObjectMapper;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.StrictAssertions.fail;

public class TestInject
{
    public static class InjectMe
    {
        private String a;

        public void setA(String a) {
            this.a = a;
        }

        public void setA(Integer a) {
            this.a = a.toString();
        }

        public String getA() {
            return a;
        }
    }

    public static class Injectee
    {
        private String b;

        @JsonCreator
        public Injectee(@JacksonInject InjectMe injectMe, @JsonProperty("b") String b) {
            this.b = b;
        }

        public String getB() {
            return b;
        }
    }

    @Test
    public void injected() {
        // ARRANGE
        InjectMe im = new InjectMe();
        ObjectMapper sut = new ObjectMapper()
            .setInjectableValues(new InjectableValues.Std().addValue(TestInject.InjectMe.class, im));
        String test = "{\"b\":\"bbb\"}";

        // ACT
        Injectee actual = null;
        try {
            actual = sut.readValue(test, Injectee.class);
        }
        catch (IOException e) {
            fail("failed to deserialize", e);
        }

        // ASSERT
        assertThat(actual.getB()).isEqualTo("bbb");
    }
}

Stacktrace:


FAILED: injected
java.lang.AssertionError: failed to deserialize
    at com.ispot.TestInject.injected(TestInject.java:64)
    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:497)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:85)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:639)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:821)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1131)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:124)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:108)
    at org.testng.TestRunner.privateRun(TestRunner.java:773)
    at org.testng.TestRunner.run(TestRunner.java:623)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:357)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:352)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:310)
    at org.testng.SuiteRunner.run(SuiteRunner.java:259)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1185)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1110)
    at org.testng.TestNG.run(TestNG.java:1018)
    at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:111)
    at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:204)
    at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:175)
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Conflicting setter definitions for property "a": com.ispot.TestInject$InjectMe#setA(1 params) vs com.ispot.TestInject$InjectMe#setA(1 params)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:269)
    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:428)
    at com.fasterxml.jackson.databind.deser.impl.PropertyBasedCreator.construct(PropertyBasedCreator.java:79)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.resolve(BeanDeserializerBase.java:549)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:296)
    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:461)
    at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:3804)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3698)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2714)
    at com.ispot.TestInject.injected(TestInject.java:61)
    ... 24 more
Caused by: java.lang.IllegalArgumentException: Conflicting setter definitions for property "a": com.ispot.TestInject$InjectMe#setA(1 params) vs com.ispot.TestInject$InjectMe#setA(1 params)
    at com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder.getSetter(POJOPropertyBuilder.java:299)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.filterBeanProps(BeanDeserializerFactory.java:592)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.addBeanProps(BeanDeserializerFactory.java:488)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:229)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:142)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:403)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:352)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)
    ... 37 more

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Comments: 15 (10 by maintainers)

Commits related to this issue

Most upvoted comments

OK. The workaround turned out to be trivial, once I thought of it - use a mixin. I just add this module to my mapper:

    @JsonIgnoreType
    private static class IgnoreMe {  };

    public Module getIgnoreJacksonModule() {
        return new SimpleModule().setMixInAnnotation(ObjectMapper.class, IgnoreMe.class);
    }

Now I can inject my class which has a property that returns an ObjectMapper.