1 2 jackson InvalidDefinitionException: Cannot find a (Map) Key deserializer for type [simple type, class XXXX]
How to reproduce 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class Student { private String name; private Integer age; } public class Class { private Map<Student, List<Student>> teamMap; } public static void main (String[] args) throws IOException { Class c = new Class (); Student leader = new Student (); leader.setAge(20 ); leader.setName("Salty Egg" ); Student s = new Student (); s.setAge(20 ); s.setName("Sweet Egg" ); Map<Student, List<Student>> map = new HashMap <>(); List<Student> list = new ArrayList <>(); list.add(s); map.put(leader, list); c.setTeamMap(map); ObjectMapper mapper = new ObjectMapper (); String json = mapper.writeValueAsString(c); System.out.println(json); Class _c = mapper.readValue(json, Class.class); System.out.println(_c.getTeamMap()); }
For serialization, there will not be any problem, and we can have the print-out
1 { "teamMap" : { "Student{name='Salty Egg', age=20}" : [ { "name" : "Sweet Egg" , "age" : 20 } ] } }
We can find that when serializing the map key, Jackson will call toString() method by default.
How to fix this The standard solution is to write the key serialize/deserialize for Jackson, what if I do not wanna it as this?!
Another workaround 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public class KeyValueContainer <K, V> { private K key; private V value; public KeyValueContainer () {} public KeyValueContainer (K key, V value) { this .key = key; this .value = value; } } public class ObjectUtils { public static <K, V> List<KeyValueContainer<K, V>> toList (Map<K, V> map) { if (map == null || map.isEmpty()) { return new ArrayList <>(); } return map.entrySet().stream() .map(e -> new KeyValueContainer <>(e.getKey(), e.getValue())) .collect(Collectors.toList()); } public static <K, V> Map<K, V> toMap (List<KeyValueContainer<K, V>> list) { if (list == null || list.isEmpty()) { return new HashMap <>(); } return list.stream() .collect(Collectors.toMap(KeyValueContainer::getKey, KeyValueContainer::getValue)); } } public class Class { @JsonIgnore private Map<Student, List<Student>> teamMap; @JsonProperty("team") private List<KeyValueContainer<Student, List<Student>>> getTeamList () { return ObjectUtils.toList(teamMap); } @JsonProperty("team") private void setTeamList (List<KeyValueContainer<Student, List<Student>>> list) { teamMap = ObjectUtils.toMap(list); } }
Then, with the same test case above, we can have the following result:
1 2 {"team":[{"key":{"name":"Salty Egg","age":20},"value":[{"name":"Sweet Egg","age":20}]}]} {Student{name='Salty Egg', age=20}=[Student{name='Sweet Egg', age=20}]}
Note:
We can have new getter & setter as private, the third-party developers will not call it with mistakes
Each time, when do serialization, it will create new List, which may bring a bit performance issue.