You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 13:06:17