Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setter confusion on assignable types #158

Closed
tsquared2763 opened this issue Oct 11, 2014 · 7 comments
Closed

Setter confusion on assignable types #158

tsquared2763 opened this issue Oct 11, 2014 · 7 comments
Milestone

Comments

@tsquared2763
Copy link

I'm running into a problem where multiple setters with assignable arguments cause confusion in handling annotations. Consider:

@JsonAutoDetect(fieldVisibility= JsonAutoDetect.Visibility.NONE,getterVisibility= JsonAutoDetect.Visibility.NONE, setterVisibility= JsonAutoDetect.Visibility.NONE, isGetterVisibility= JsonAutoDetect.Visibility.NONE)
Class Foo {
    private String bar;

    @JsonProperty
    public String getBar() {
        return bar;
    }

    @JsonProperty
    public void setBar(String bar) {
        this.bar = bar;
    }

    public void setBar(Serializable bar) {
        this.bar = bar.toString();
    }
}

I would expect to be able to marshall to/from Json using the annotated methods, but what happens is that (somewhat randomly based on the reflection iteration) I may get the setBar(Serializable) method mapped to the deserializer, which of course fails as an abstract mutator. Both methods occupy the same slot in the properties list during parser initialization.

The source of the problem seems to be at the following location - including foreshadowing comment by tatu on April 7 2009.. it didn't work for me sadly.

Looking for workaround ideas, other than 'don't do that'. Any way I can kick the setBar(Serialiazable) out of the list before the properties are created? Setting @JsonIgnore is too late unfortunately, the method list is already created when that is considered.

In com.fasterxml.jackson.databind.introspect.MemberKey.java

    @Override
    public boolean equals(Object o)
    {
        if (o == this) return true;
        if (o == null) return false;
        if (o.getClass() != getClass()) {
            return false;
        }
        MemberKey other = (MemberKey) o;
        if (!_name.equals(other._name)) {
            return false;
        }
        Class<?>[] otherArgs = other._argTypes;
        int len = _argTypes.length;
        if (otherArgs.length != len) {
            return false;
        }
        for (int i = 0; i < len; ++i) {
            Class<?> type1 = otherArgs[i];
            Class<?> type2 = _argTypes[i];
            if (type1 == type2) {
                continue;
            }
            /* 23-Feb-2009, tatu: Are there any cases where we would have to
             *   consider some narrowing conversions or such? For now let's
             *   assume exact type match is enough
             */
            /* 07-Apr-2009, tatu: Indeed there are (see [JACKSON-97]).
             *    This happens with generics when a bound is specified.
             *    I hope this works; check here must be transitive
             */
            if (type1.isAssignableFrom(type2) || type2.isAssignableFrom(type1)) {
                continue;
            }
            return false;
        }
        return true;
    }
@cowtowncoder
Copy link
Member

Hmmh. No, this case should work just fine; introspection functionality should be able to resolve it even if visibility was not changed, to choose the explicit setter over non-annotated one.

I'll see if I can reproduce your issue locally.

@tsquared2763
Copy link
Author

Thanks for the fast response ctc. It seems to go wrong right here - as Serializable is assignable from String, so it thinks it's the same (or similar enough) method....

            if (type1.isAssignableFrom(type2) || type2.isAssignableFrom(type1)) {
                continue;
            }

all this happens in AnnotatedClass.addMemberMethods, before any annotations are considered.

@cowtowncoder
Copy link
Member

For me that test passes without problems, on master (2.5.0-SNAPSHOT); should be same code as 2.4.3.

Which version are you having problems with?

@tsquared2763
Copy link
Author

On latest release:

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.4.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.4.3</version>
        </dependency>

A more complete test...

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.io.Serializable;

@JsonAutoDetect(fieldVisibility= JsonAutoDetect.Visibility.NONE,getterVisibility= JsonAutoDetect.Visibility.NONE, setterVisibility= JsonAutoDetect.Visibility.NONE, isGetterVisibility= JsonAutoDetect.Visibility.NONE)
public class Foo {
    private String bar;

    @JsonProperty
    public String getBar() {
            return bar;
    }

    @JsonProperty
    public void setBar(String bar) {
            this.bar = bar;
    }

    public void setBar(Serializable bar) {
            this.bar = bar.toString();
    }
}

Test...

import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.MapperFeature
import com.fasterxml.jackson.databind.ObjectMapper
import org.junit.Test
import static org.junit.Assert.assertEquals;

class JsonMarshalTest {

    @Test
    public void jsonMarshalTest() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(MapperFeature.AUTO_DETECT_SETTERS, false);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        Foo foo = new Foo();
        foo.setBar(new Double(3.14159));

        String json = mapper.writeValueAsString(foo);
        Foo cloneFoo = mapper.readValue(json, Foo.class);

        assertEquals("3.14159", cloneFoo.getBar());

    }

}

Result...

com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.io.Serializable, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
 at [Source: {"bar":"3.14159"}; line: 1, column: 2] (through reference chain: com.modelshop.datacore.marshaller.Foo["bar"])
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
    at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:771)
    at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:140)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:538)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:99)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:238)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:118)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3051)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2146)
    at com.fasterxml.jackson.databind.ObjectMapper$readValue$3.call(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:120)
    at com.modelshop.datacore.marshaller.JsonMarshalTest.jsonMarshalTest(JsonMarshalTest.groovy:28)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:77)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:195)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

My workaround...

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.io.Serializable;

@JsonAutoDetect(fieldVisibility= JsonAutoDetect.Visibility.NONE,getterVisibility= JsonAutoDetect.Visibility.NONE, setterVisibility= JsonAutoDetect.Visibility.NONE, isGetterVisibility= JsonAutoDetect.Visibility.NONE)
public class Foo {
    private String bar;

    @JsonProperty
    public String getBar() {
            return bar;
    }

    public void setBar(String bar) {
            this.bar = bar;
    }

    @JsonIgnore
    public void setBar(Serializable bar) {
            this.bar = bar.toString();
    }

    @JsonProperty("bar")
    private void setBar_Json(String bar) {
        setBar(bar);
    }
}

@cowtowncoder
Copy link
Member

Actually looks like I can sometimes reproduce this: order of methods as returned by reflection is what causes or hides the issue. So I think I should be able to figure out why overriding handling does not work as expected.

@cowtowncoder cowtowncoder added this to the 2.4.4 milestone Oct 14, 2014
@cowtowncoder
Copy link
Member

@tsquared2763 Yes, you were absolutely correct on root cause. I can't recall the original reasoning, but equality really should not be extended that way, at this point in processing. Overlaps/conflicts will be properly resolved where possible at a later point; at this point identity must be preserved.

Fixed for 2.4.4 (and 2.5)

@tsquared2763
Copy link
Author

Thank you Tatu.

Sent with AquaMail for Android
http://www.aqua-mail.com

On October 14, 2014 2:52:59 PM Tatu Saloranta [email protected] wrote:

@tsquared2763 Yes, you were absolutely correct on root cause. I can't
recall the original reasoning, but equality really should not be extended
that way, at this point in processing. Overlaps/conflicts will be properly
resolved where possible at a later point; at this point identity must be
preserved.


Reply to this email directly or view it on GitHub:
#158 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants