如何绕过泛型Map序列化器的运行时类型擦除问题
解决Map<K,V>序列化器反序列化时的泛型类型擦除问题
这个问题本质上是Java泛型的类型擦除在搞鬼——编译时的K和V参数到了运行时会被擦除成Object,所以你用new TypeReference<List<Entry<K,V>>>()的时候,Jackson根本不知道实际要反序列化的键和值是什么类型,只能把嵌套的Entity2转成默认的Map。
下面给你几个通用的解决方案,从简单到灵活,覆盖普通类型和复杂泛型类型的场景:
方案1:传入Class类型参数,用Jackson TypeFactory构建具体类型
如果你的K和V都是非泛型的普通类(比如String、自定义Entity1/Entity2),可以直接传入它们的Class对象,然后用Jackson的TypeFactory手动构建出List<Entry<K,V>>的具体类型:
public <K, V> Map<K, V> deserializeMap(String json, Class<K> keyClass, Class<V> valueClass) throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); // 先构建Entry<K,V>的类型 Type entryType = TypeFactory.defaultInstance() .constructParametricType(Map.Entry.class, keyClass, valueClass); // 再构建List<Entry<K,V>>的类型 Type listType = TypeFactory.defaultInstance() .constructParametricType(List.class, entryType); // 反序列化成条目列表,再转回Map List<Map.Entry<K, V>> entries = mapper.readValue(json, listType); Map<K, V> result = new HashMap<>(); for (Map.Entry<K, V> entry : entries) { result.put(entry.getKey(), entry.getValue()); } return result; }
调用的时候直接传对应的Class就行:
Map<String, Entity2> map = deserializeMap(jsonString, String.class, Entity2.class);
方案2:支持泛型类型的键/值(比如List、Map<Integer, Entity>)
如果你的K或V本身是泛型类型(比如List<String>),Class对象就不够用了,这时候需要传入Type类型的参数:
public <K, V> Map<K, V> deserializeGenericMap(String json, Type keyType, Type valueType) throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); Type entryType = TypeFactory.defaultInstance() .constructParametricType(Map.Entry.class, keyType, valueType); Type listType = TypeFactory.defaultInstance() .constructParametricType(List.class, entryType); List<Map.Entry<K, V>> entries = mapper.readValue(json, listType); Map<K, V> result = new HashMap<>(); entries.forEach(result::put); return result; }
比如要处理键为List<String>、值为Entity2的场景:
// 构建泛型键的Type Type keyType = TypeFactory.defaultInstance() .constructParametricType(List.class, String.class); Map<List<String>, Entity2> map = deserializeGenericMap(jsonString, keyType, Entity2.class);
方案3:封装成通用序列化器类
如果这个序列化器是你项目中常用的,可以把它封装成一个带类型参数的类,初始化时就传入键和值的类型信息,避免每次调用都传参数:
public class GenericMapSerializer<K, V> { private final Type keyType; private final Type valueType; private final ObjectMapper mapper; // 非泛型类型用Class初始化 public GenericMapSerializer(Class<K> keyClass, Class<V> valueClass) { this.keyType = keyClass; this.valueType = valueClass; this.mapper = new ObjectMapper(); // 这里可以添加自定义配置,比如注册键的序列化/反序列化器 } // 泛型类型用Type初始化 public GenericMapSerializer(Type keyType, Type valueType) { this.keyType = keyType; this.valueType = valueType; this.mapper = new ObjectMapper(); } // 序列化:Map转成List<Entry>的JSON public String serialize(Map<K, V> map) throws JsonProcessingException { List<Map.Entry<K, V>> entries = new ArrayList<>(map.entrySet()); return mapper.writeValueAsString(entries); } // 反序列化:JSON转回Map public Map<K, V> deserialize(String json) throws JsonProcessingException { Type entryType = TypeFactory.defaultInstance() .constructParametricType(Map.Entry.class, keyType, valueType); Type listType = TypeFactory.defaultInstance() .constructParametricType(List.class, entryType); List<Map.Entry<K, V>> entries = mapper.readValue(json, listType); Map<K, V> result = new HashMap<>(); entries.forEach(result::put); return result; } }
使用示例:
// 普通类型场景 GenericMapSerializer<String, Entity2> serializer = new GenericMapSerializer<>(String.class, Entity2.class); String json = serializer.serialize(myMap); Map<String, Entity2> deserializedMap = serializer.deserialize(json); // 泛型键场景 Type keyType = TypeFactory.defaultInstance().constructParametricType(Map.class, Integer.class, String.class); GenericMapSerializer<Map<Integer, String>, Entity2> genericSerializer = new GenericMapSerializer<>(keyType, Entity2.class);
额外注意:复杂自定义类型作为键的处理
如果你的键是自定义对象(比如Entity1),Jackson默认没法直接把字符串键转回对象,这时候需要自定义KeySerializer和KeyDeserializer:
// 自定义键序列化器:把Entity1转成可反序列化的字符串 public class Entity1KeySerializer extends StdSerializer<Entity1> { private final ObjectMapper innerMapper = new ObjectMapper(); public Entity1KeySerializer() { super(Entity1.class); } @Override public void serialize(Entity1 value, JsonGenerator gen, SerializerProvider provider) throws IOException { // 把Entity1序列化成JSON字符串作为键 gen.writeFieldName(innerMapper.writeValueAsString(value)); } } // 自定义键反序列化器:把字符串转回Entity1 public class Entity1KeyDeserializer extends StdKeyDeserializer { private final ObjectMapper innerMapper = new ObjectMapper(); public Entity1KeyDeserializer() { super(TYPE_OBJECT, Entity1.class); } @Override public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { return innerMapper.readValue(key, Entity1.class); } }
然后在序列化器的ObjectMapper中注册这些处理器:
this.mapper.registerModule(new SimpleModule() .addKeySerializer(Entity1.class, new Entity1KeySerializer()) .addKeyDeserializer(Entity1.class, new Entity1KeyDeserializer()));
这样Jackson就能正确处理自定义对象作为键的序列化和反序列化了。
内容的提问来源于stack exchange,提问作者Kalle Richter
相关产品推荐
相关产品推荐

