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

StdKeyDeserializer can erroneously use a static factory method with more than one argument #1429

Closed
narbsy opened this issue Oct 22, 2016 · 2 comments
Milestone

Comments

@narbsy
Copy link

narbsy commented Oct 22, 2016

While investigating an issue, I found that there was different behavior for normal deserializers and key deserializers where deserializing a value as a field works as expected, but as a map key fails with "not a valid representation: wrong number of arguments".

A basic example:

import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;

import java.io.IOException;
import java.util.Map;
import java.util.Map.Entry;

import static org.junit.Assert.assertEquals;

public class KeyVsFieldTest {
    @Test
    public void deserializeAsField() throws IOException {
        AsField as_field = new ObjectMapper().readValue("{\"name\": \"first.last\"}", AsField.class);
        assertEquals(as_field.getName()._firstname, "first");
        assertEquals(as_field.getName()._lastname, "last");
    }

    @Test
    public void deserializeAsKey() throws IOException {
        Map<FullName, Double> map =
            new ObjectMapper().readValue("{\"first.last\": 42}", new TypeReference<Map<FullName, Double>>() {
            });
       /* 
          Fails with: com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not construct Map key of type KeyVsFieldTest$FullName from String "first.last": not a valid representation: wrong number of arguments
 at [Source: java.io.StringReader@7113b13f; line: 1, column: 2]
       */

        Entry<FullName, Double> entry = map.entrySet().iterator().next();

        assertEquals(entry.getKey()._firstname, "first");
        assertEquals(entry.getKey()._lastname, "last");
        assertEquals(entry.getValue().doubleValue(), 42, 0);
    }

    public static class AsField {
        private final FullName _name;

        public AsField(@JsonProperty("name") FullName aName) {
            _name = aName;
        }

        public FullName getName() {
            return _name;
        }
    }

    public static class FullName {
        private final String _firstname;
        private final String _lastname;

        private FullName(String firstname, String lastname) {
            _firstname = firstname;
            _lastname = lastname;
        }

        @JsonCreator
        public static FullName valueOf(String value) {
            String[] mySplit = value.split("\\.");
            return new FullName(mySplit[0], mySplit[1]);
        }

        public static FullName valueOf(String firstname, String lastname) {
            return new FullName(firstname, lastname);
        }

        @JsonValue
        @Override
        public String toString() {
            return _firstname + "." + _lastname;
        }
    }
}

It looks like this is because in BasicBeanDescriptor, findFactoryMethod has an incorrect assumption about the contents of _classInfo.getStaticMethods(), which will have any method named valueOf and static methods annotated with @JsonCreator:

    @Override
    public Method findFactoryMethod(Class<?>... expArgTypes)
    {
        // So, of all single-arg static methods:
        for (AnnotatedMethod am : _classInfo.getStaticMethods()) {
            if (isFactoryMethod(am)) {
                // And must take one of expected arg types (or supertype)
                Class<?> actualArgType = am.getRawParameterType(0);
                for (Class<?> expArgType : expArgTypes) {
                    // And one that matches what we would pass in
                    if (actualArgType.isAssignableFrom(expArgType)) {
                        return am.getAnnotated();
                    }
                }
            }
        }
        return null;
    }

This can be worked around by annotating static factory methods not intended to be used as @JsonCreators with @JsonIgnore, due to the resolution in _classInfo.getStaticMethods(), so is not really urgent.

Please let me know if you have any questions about the issue!

Thanks,
Chris

cowtowncoder added a commit that referenced this issue Oct 24, 2016
@cowtowncoder cowtowncoder changed the title StdKeyDeserializer can erroneously use a static factory method with more than one argument StdKeyDeserializer can erroneously use a static factory method with more than one argument Oct 25, 2016
@cowtowncoder cowtowncoder added this to the 2.8.5 milestone Oct 25, 2016
@narbsy
Copy link
Author

narbsy commented Oct 25, 2016

Thank you for the quick response! Have a lovely day :)

@cowtowncoder
Copy link
Member

@narbsy thank you for reporting this -- it was an edge case, easy to fix once known.

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