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> {
}
}
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);