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

无需反射与父类注解,基于@type隐式反序列化Jackson多态对象

问题:Jackson反序列化多态表达式接口时无法解析类型ID

场景与代码结构

现有代码结构如下,由于具体表达式(如AnotherExpression)由不同团队在独立模块维护扩展,需避免在父类中添加所有类型信息与子类注解,且大概率需规避反射机制:

public interface Expression<T> {
    // 省略内容
}

public interface BooleanExpression extends Expression<Boolean> {
    // 省略内容
}

public class AnotherExpression implements BooleanExpression {
    private final boolean value;
    // 省略构造、getter等实现
}

public class Wrapper {
    public Expression expression;
}

期望的反序列化操作

想要反序列化如下JSON字符串:

String json =
"""
{ "expression": {
  "@type": "AnotherExpression",
  "value": true
}} 
""";

ObjectMapper mapper = new ObjectMapper();
Wrapper exp = mapper.readValue(json, Wrapper.class);

报错信息

运行时抛出异常:

com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'AnotherExpression' as a subtype of [...].Expression  known type ids = [] (for POJO property 'constant')

已尝试的无效方案

  • 使用objectMapper.registerSubtypes方法(但该方法依赖反射,不符合需求)
  • 在Expression上添加@JsonTypeInfo与子类注解,未解决问题

解决方案:自定义类型解析器(无反射+模块化扩展)

核心思路是实现TypeIdResolver,手动维护类型ID与具体类的映射,允许外部模块自主注册类型,无需修改父类或给子类加注解。

1. 实现自定义类型解析器

public class ExpressionTypeIdResolver extends TypeIdResolverBase {
    // 线程安全的类型映射表,供外部模块注册
    private static final Map<String, Class<? extends Expression<?>>> TYPE_MAP = new ConcurrentHashMap<>();
    private JavaType baseType;

    // 对外暴露的注册方法,让各模块自行注册类型
    public static void registerType(String typeId, Class<? extends Expression<?>> clazz) {
        TYPE_MAP.put(typeId, clazz);
    }

    @Override
    public void init(JavaType baseType) {
        this.baseType = baseType;
    }

    @Override
    public String idFromValue(Object value) {
        return value.getClass().getSimpleName();
    }

    @Override
    public String idFromValueAndType(Object value, Class<?> suggestedType) {
        return idFromValue(value);
    }

    @Override
    public JavaType typeFromId(DatabindContext context, String id) {
        Class<? extends Expression<?>> clazz = TYPE_MAP.get(id);
        if (clazz == null) {
            throw new IllegalArgumentException("未知表达式类型: " + id);
        }
        return context.constructSpecializedType(baseType, clazz);
    }

    @Override
    public JsonTypeInfo.Id getMechanism() {
        return JsonTypeInfo.Id.NAME;
    }
}

2. 给父接口配置类型解析规则

仅需在Expression接口上添加一次注解,无需修改任何子类:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@type")
@JsonTypeIdResolver(ExpressionTypeIdResolver.class)
public interface Expression<T> {
    // 省略内容
}

3. 外部模块注册类型

在AnotherExpression所在模块的初始化逻辑中,手动注册类型映射:

// 比如模块启动时调用
ExpressionTypeIdResolver.registerType("AnotherExpression", AnotherExpression.class);

4. 反序列化代码保持不变

原有的反序列化代码无需修改,即可正常解析JSON。

方案优势

  • 无需在父类硬编码所有子类类型,完美支持跨模块扩展
  • 完全规避反射,类型映射通过手动注册实现
  • 子类无需添加任何Jackson注解,不影响各团队独立维护

内容的提问来源于stack exchange,提问作者Michail Michailidis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 12:43:22