javajacksondeserialization

Jackson custom map deserialization throws "Cannot find a (Map) Key deserializer" exception


I have several sets of parameters that comes from client and I need to parse them. I make a custom class extended from a HashMap. It is parameterized with map key class which is enum itself. Using Enums here is dictated by its use in other various parts of the application.

Below is a working code snippet with commented state of issue I have. I need to make case (1) to be working but it throws "Cannot find a (Map) Key deserializer" exception.

Cases (2) and (3) I write here as my experiments. I'm not sure about case (2) error as I can instantiate new ClientParams<FirstParams>() with no problem. Case (3) is working and almost what I need, but nonetheless I need to have separate ClientParams<P> class for parameters map.

public class Main {
    public static final ObjectMapper mapper = new ObjectMapper();

    public static void main(String[] args) throws JsonProcessingException {
        new Main().doParse();
    }
    public void doParse() throws JsonProcessingException {
        String json = "{\"p1\": \"str1\"}";

        // case (1)
        new FirstParser().parse(json);
        // Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
        // Cannot find a (Map) Key deserializer for type [simple type, class org.example.Main$Param]

        // case (2)
        mapper.readValue(json, new TypeReference<ClientParams<FirstParams>>() {});
        // Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
        // Cannot construct instance of `org.example.Main$ClientParams` (no Creators, 
        // like default constructor, exist): no default constructor found
        
        // case (3)
        mapper.readValue(json, new TypeReference<HashMap<FirstParams, Object>>() {});
        // OK
    }
    interface Param {
        String key();
    }
    class FirstParser extends Parser<FirstParams> {}
    class Parser<P extends Param> {
        ClientParams<P> parse(String str) throws JsonProcessingException {
            return mapper.readValue(str, new TypeReference<>() {});
        }
    }
    enum FirstParams implements Param {
        P1("p1"), P2("p2");
        private final String key;
        FirstParams(String key) {this.key = key;}
        public String key() {return key;}
        private static final Map<String, FirstParams> mapByKey =
            Stream.of(values()).collect(toMap(t -> t.key, identity()));
        @JsonCreator
        public static FirstParams fromKey(String key) {return mapByKey.get(key);}
    }
    class ClientParams<P extends Param> extends HashMap<P, Object> {
    }
}

Solution

  • Looking at the working case (3) once again I made a fix for case (1) so it can work:

    class Parser<P extends Param> {
        ClientParams<P> parse(String str, Class<P> clazz) throws JsonProcessingException {
            JavaType javaType = mapper.getTypeFactory().constructParametricType(HashMap.class, clazz, Object.class);
            return new ClientParams<>(mapper.readValue(str, javaType));
        }
    }
    class ClientParams<P extends Param> extends HashMap<P, Object> {
        public ClientParams(Map<? extends P, ?> m) {
            super(m);
        }
    }
    // case (1) now works with additional passing parameter type class
    new FirstParser().parse(json, FirstParams.class);