如何为Jackson多态反序列化的鉴别器属性添加别名?
解决方案:为Jackson多态鉴别器添加别名
要实现鉴别器属性type和别名?的双支持,我们可以通过扩展Jackson的类型解析逻辑,完全基于注解配置实现,不需要修改ObjectMapper或JSON结构。核心思路是自定义类型解析器构建器和类型反序列化器,让Jackson在解析类型ID时同时检查主属性和别名属性。
步骤1:自定义类型解析器构建器
这个类负责创建支持别名的类型反序列化器,并从注解参数中读取别名配置:
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.DeserializationConfig; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import com.fasterxml.jackson.databind.jsontype.TypeIdResolver; import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder; public class AliasTypeResolverBuilder extends StdTypeResolverBuilder { private String aliasProperty; public AliasTypeResolverBuilder() {} @Override public TypeResolverBuilder<?> init(JsonTypeInfo.Id id, TypeIdResolver idRes) { super.init(id, idRes); // 从@JsonTypeInfo的parameters参数中读取别名 if (_typeInfo != null && _typeInfo.parameters() != null && _typeInfo.parameters().length > 0) { this.aliasProperty = _typeInfo.parameters()[0]; } return this; } @Override public TypeDeserializer buildTypeDeserializer(DeserializationConfig config, JavaType baseType, BeanDescription beanDesc) { TypeIdResolver idResolver = idResolver(config, baseType, beanDesc, JsonTypeInfo.Id.NAME, null); return new AliasTypeDeserializer(baseType, idResolver, _typeProperty, _typeIdVisible, _defaultImpl, aliasProperty); } }
步骤2:自定义类型反序列化器
重写类型ID的提取逻辑,优先读取主属性type,如果不存在则读取别名?:
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.jsontype.TypeIdResolver; import com.fasterxml.jackson.databind.jsontype.impl.StdTypeDeserializer; import java.io.IOException; public class AliasTypeDeserializer extends StdTypeDeserializer { private final String aliasProperty; public AliasTypeDeserializer(JavaType baseType, TypeIdResolver idRes, String typePropertyName, boolean typeIdVisible, Class<?> defaultImpl, String aliasProperty) { super(baseType, idRes, typePropertyName, typeIdVisible, defaultImpl); this.aliasProperty = aliasProperty; } @Override protected String _extractTypeId(JsonParser p, DeserializationContext ctxt) throws IOException { // 先尝试读取主鉴别器属性"type" String typeId = super._extractTypeId(p, ctxt); if (typeId != null && !typeId.isEmpty()) { return typeId; } // 主属性不存在时,重置JSON指针并读取别名属性"?" p.getCodec().readTree(p); return p.getCodec().readTree(p).get(aliasProperty).asText(); } }
步骤3:修改Shape类的注解配置
在@JsonTypeInfo中指定自定义的构建器和别名参数:
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo.As; import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; @JsonTypeInfo( use = Id.NAME, include = As.PROPERTY, property = "type", builder = AliasTypeResolverBuilder.class, parameters = "?" // 指定别名属性名 ) @JsonSubTypes({ @JsonSubTypes.Type(value = Square.class, name = "square"), @JsonSubTypes.Type(value = Circle.class, name = "circle") }) static abstract class Shape { abstract public String name(); } static class Square extends Shape { @JsonProperty("A") public float width; @Override public String name() { return "square"; } } static class Circle extends Shape { @JsonProperty("A") public float diameter; @Override public String name() { return "circle"; } }
步骤4:验证测试用例
原测试用例无需修改,即可同时支持两种JSON格式:
import static org.assertj.core.api.Assertions.assertThat; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; public class DiscriminatorAliasTest { // ... Shape、Square、Circle类定义 ... @Test public void testDiscriminator() throws Exception { ObjectMapper mapper = new ObjectMapper(); // 原有格式正常解析 String squareJson = "{\"type\":\"square\", \"A\": 1.0 }"; Shape square = mapper.readerFor(Shape.class).readValue(squareJson); assertThat(square.name()).isEqualTo("square"); // 别名格式同样可以解析 String squareJsonWithAlternativePropertyName = "{\"?\":\"square\", \"A\": 1.0 }"; Shape alsoSquare = mapper.readerFor(Shape.class).readValue(squareJsonWithAlternativePropertyName); assertThat(alsoSquare.name()).isEqualTo("square"); } }
方案符合限制条件说明
- 不使用
Id.DEDUCTION:完全依赖Id.NAME类型鉴别器,通过属性值区分子类 - 无需操作ObjectMapper:所有配置通过注解完成,不需要修改或初始化
ObjectMapper的额外逻辑 - 不修改JSON结构:反序列化过程中自动处理别名属性,无需提前修改JSON内容
- 复用原生机制:基于Jackson原生的多态反序列化逻辑,不需要手动构造
Shape实例
内容的提问来源于stack exchange,提问作者Max Bo
相关产品推荐
相关产品推荐

