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

Converter annotation not honored for abstract types #795

Closed
myrosia opened this issue May 15, 2015 · 7 comments
Closed

Converter annotation not honored for abstract types #795

myrosia opened this issue May 15, 2015 · 7 comments
Milestone

Comments

@myrosia
Copy link

myrosia commented May 15, 2015

I have a use case (described also in this Stackoverflow question: http://stackoverflow.com/questions/30264115/how-to-use-jackson-deserializer-converter-correctly) where I would like to implement custom parsing for string values into types based on a legacy parser. I implemented a converter extending StdConverter that takes a string and runs my legacy parser to parse it in my desired type. I then tried to use @JsonDeserialize with converter argument.

This works when my field is a concrete class. But if my field is declared to be of an abstract type, even if the converter correctly returns a concrete instance, I get an exception.

More specifically, in the test case below, I would expect both testNonAbstractTypeDeserialization and testAbstractTypeDeserialization to pass, because they use very similar converters. But in practice (Jackson 2.5.0) testNonAbstractTypeDeserialization passes, and testAbstractTypeDeserialization fails with the following exception:

com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of JacksonTest$AbstractCustomType, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
 at [Source: {"customField": "customString"}; line: 1, column: 2]

The JUnit test case is below

import java.io.IOException;

import org.junit.Test;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.Converter;
import com.fasterxml.jackson.databind.util.StdConverter;

import static org.junit.Assert.*;

public class JacksonTest {
    public static abstract class AbstractCustomType {
        private final String value;
        public AbstractCustomType(String v) {
            this.value = v;
        }
    }

    public static class ConcreteCustomType extends AbstractCustomType {
        public ConcreteCustomType(String v) {
            super(v);
        }
    }

    public static class AbstractCustomTypeDeserializationConverter extends StdConverter<String, AbstractCustomType>{

        @Override
        public AbstractCustomType convert(String arg) {
            return new ConcreteCustomType(arg);
        }
    }


    public static class AbstractCustomTypeUser {
        @JsonProperty
        @JsonDeserialize(converter = AbstractCustomTypeDeserializationConverter.class)
        private final AbstractCustomType customField;

        @JsonCreator AbstractCustomTypeUser(@JsonProperty("customField") AbstractCustomType customField) {
            this.customField = customField;
        }
    }


    public static  class NonAbstractCustomType {
        private final String value;
        public NonAbstractCustomType(String v) {
            this.value = v;
        }
    }


    public static class NonAbstractCustomTypeDeserializationConverter extends StdConverter<String, NonAbstractCustomType>{

        @Override
        public NonAbstractCustomType convert(String arg) {
            return new NonAbstractCustomType(arg);
        }
    }


    public static class NonAbstractCustomTypeUser {
        @JsonProperty
        @JsonDeserialize(converter = NonAbstractCustomTypeDeserializationConverter.class)
        private final NonAbstractCustomType customField;

        @JsonCreator NonAbstractCustomTypeUser(@JsonProperty("customField") NonAbstractCustomType customField) {
            this.customField = customField;
        }
    }

    private static final ObjectMapper JSON_MAPPER = new ObjectMapper();

    @Test
    public void testAbstractTypeDeserialization() throws IOException {
        String test="{\"customField\": \"customString\"}";
        AbstractCustomTypeUser cu = JSON_MAPPER.readValue(test, AbstractCustomTypeUser.class);
        assertNotNull(cu);
    }

    @Test
    public void testNonAbstractDeserialization() throws IOException {
        String test="{\"customField\": \"customString\"}";
        NonAbstractCustomTypeUser cu = JSON_MAPPER.readValue(test, NonAbstractCustomTypeUser.class);
        assertNotNull(cu);
    }

}
@cowtowncoder
Copy link
Member

Conversion into abstract type should be perfectly legal. I tried a simplified example which worked, so now I need to dig bit deeper to see why your case fails.

cowtowncoder added a commit that referenced this issue May 18, 2015
@cowtowncoder
Copy link
Member

Seems to be a problem with combination of Creator and converter, for what that is worth: if customField was public and there was no constructor (just no-arg default one), test would pass.
So at least I know what to try to diagnose: either annotation from field is not affecting one for constructor parameter, or constructor parameters do not check for existence of converter.

@cowtowncoder
Copy link
Member

As to non-abstract type: it is possible it might work just because of existence of public NonAbstractCustomType(String v), which is found for deserialization even if converter was ignored.
I haven't verified this is what happens, but that would explain why that happens to work.

@cowtowncoder cowtowncoder added this to the 2.6.0 milestone May 19, 2015
@cowtowncoder
Copy link
Member

Ok. Turns out the fix was bit non-trivial, and it can only be done in master branch for 2.6.
So for earlier versions, the work-around is needed, to use setter (or field) instead of constructor parameter, for type that needs converter.
This is unfortunate but on plus I am hoping to start release candidates for 2.6 this week, so 2.6.0-rc1 should be out soon.

@myrosia
Copy link
Author

myrosia commented May 19, 2015

OK, thanks. Using a setter works around the issue for me, thanks. I will watch out for 2.6 release and re-test there.

@riwnodennyk
Copy link

riwnodennyk commented Jun 2, 2016

For me it's still not working in 2.7.4 when abstract class is a value type of the Map.
E.g.:

@JsonProperty
@JsonDeserialize(contentConverter = StringToUri.class)
public abstract Map<Integer, Uri> map();
public class StringToUri extends StdConverter<String, Uri> {
    @Override
    public Uri convert(String it) {
        return Uri.parse(it);
    }
}

where Uri is abstract. I get com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of android.net.Uri, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information exception.

@cowtowncoder
Copy link
Member

@riwnodennyk Ok. Could you file another issue, since root cause might be different? It will also be easier to keep release notes clean wrt release in which various issues were resolved. You can have a reference back to this issue from description.

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

3 participants