如何在Jackson自定义反序列化器中获取输出字段的泛型类型
实现支持泛型的Jackson序列化/反序列化器处理ThirdPartyMap
看起来你想要让自定义的序列化/反序列化器具备泛型能力,不用硬编码MyKey和MyValue作为ThirdPartyMap的类型参数。下面是具体的实现方案,核心是利用Jackson的上下文序列化/反序列化器来动态获取泛型类型信息:
1. 泛型序列化器(MyGenericSerializer)
首先我们实现支持泛型的序列化器,解决默认用key.toString()作为字段名的问题。这里假设我们把ThirdPartyMap序列化成键值对数组(这样能完整保留键的结构化信息),你可以根据需求调整序列化格式:
public class MyGenericSerializer extends JsonSerializer<ThirdPartyMap<?, ?>> implements ContextualSerializer { private JavaType keyType; private JavaType valueType; // 无参构造器,用于Jackson初始化 public MyGenericSerializer() {} // 带类型参数的构造器,用于上下文初始化时传递实际类型 private MyGenericSerializer(JavaType keyType, JavaType valueType) { this.keyType = keyType; this.valueType = valueType; } @Override public void serialize(ThirdPartyMap<?, ?> value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeStartArray(); for (Map.Entry<?, ?> entry : value.entrySet()) { gen.writeStartObject(); // 序列化键为结构化对象 gen.writeFieldName("key"); serializers.findValueSerializer(keyType).serialize(entry.getKey(), gen, serializers); // 序列化值 gen.writeFieldName("value"); serializers.findValueSerializer(valueType).serialize(entry.getValue(), gen, serializers); gen.writeEndObject(); } gen.writeEndArray(); } @Override public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { // 获取ThirdPartyMap的实际泛型类型参数 JavaType mapType = property.getType(); JavaType keyType = mapType.containedType(0); JavaType valueType = mapType.containedType(1); return new MyGenericSerializer(keyType, valueType); } }
2. 泛型反序列化器(MyGenericDeserializer)
接下来是关键的泛型反序列化器,通过实现ContextualDeserializer来动态获取MyKey和MyValue的实际类型:
public class MyGenericDeserializer extends JsonDeserializer<ThirdPartyMap<?, ?>> implements ContextualDeserializer { private JavaType keyType; private JavaType valueType; // 无参构造器 public MyGenericDeserializer() {} // 带类型参数的构造器 private MyGenericDeserializer(JavaType keyType, JavaType valueType) { this.keyType = keyType; this.valueType = valueType; } @Override public ThirdPartyMap<?, ?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { ThirdPartyMap<Object, Object> map = new ThirdPartyMap<>(); // 按数组格式反序列化(和序列化逻辑对应) JsonNode arrayNode = p.readValueAsTree(); if (arrayNode.isArray()) { for (JsonNode node : arrayNode) { // 反序列化键 Object key = ctxt.readTreeAsValue(node.get("key"), keyType); // 反序列化值 Object value = ctxt.readTreeAsValue(node.get("value"), valueType); map.put(key, value); } } return map; } @Override public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { // 获取ThirdPartyMap的泛型类型参数 JavaType mapType = property.getType(); JavaType keyType = mapType.containedType(0); JavaType valueType = mapType.containedType(1); return new MyGenericDeserializer(keyType, valueType); } }
3. 调整Container类的注解
现在把原来的硬编码序列化/反序列化器替换成我们的泛型版本,修改后的Container类如下:
@JsonDeserialize(builder = ContainerBuilder.class) class Container { @JsonSerialize(using = MyGenericSerializer.class) @JsonDeserialize(using = MyGenericDeserializer.class) ThirdPartyMap<MyKey, MyValue> map; // 私有构造器,由Builder创建 private Container(ContainerBuilder builder) { this.map = builder.map; } // Builder类保持原有逻辑即可 public static class ContainerBuilder { private ThirdPartyMap<MyKey, MyValue> map; public ContainerBuilder map(ThirdPartyMap<MyKey, MyValue> map) { this.map = map; return this; } public Container build() { return new Container(this); } } }
4. 验证泛型能力
现在这个序列化/反序列化器可以支持任意类型参数的ThirdPartyMap,比如你如果有另一个类:
class AnotherContainer { @JsonSerialize(using = MyGenericSerializer.class) @JsonDeserialize(using = MyGenericDeserializer.class) ThirdPartyMap<String, Integer> anotherMap; }
不需要修改序列化/反序列化器的代码,它会自动获取String和Integer作为类型参数进行处理。
关键说明
- ContextualSerializer/ContextualDeserializer:这两个接口是实现泛型支持的核心,它们允许我们在序列化/反序列化时,从Bean属性的上下文信息中获取到实际的泛型类型参数,而不用提前硬编码。
- JavaType:Jackson提供的类型封装类,用来表示各种Java类型(包括泛型),通过
containedType(int index)方法可以获取泛型的类型参数。 - 序列化格式:示例中把
ThirdPartyMap序列化成键值对数组,你可以根据自己的需求调整(比如如果需要保留对象格式,可以把键序列化成JSON字符串,但这样可能丢失结构化信息,所以数组格式更适合保留键的结构化特征)。
内容的提问来源于stack exchange,提问作者Troy Daniels




