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

无类型信息下AbstractEnum子类反序列化的工厂调用需求

解决枚举类型反序列化时调用工厂而非构造方法的方案

我之前也碰到过类似的问题——JSON里只有枚举的ID,没法直接推断具体枚举类型,得靠业务对象的属性上下文来判断,还要绕开构造方法用工厂实例化。结合你的场景,给你几个实用的实现思路:

核心思路

你的关键诉求是:反序列化时根据Obj的属性类型(比如是EnumA还是EnumB的属性),调用对应工厂方法获取实例,而不是直接调用枚举的构造/默认反序列化逻辑。本质上是利用属性的类型上下文来匹配工厂方法,下面分主流序列化框架举例:


方案1:针对每个枚举写自定义反序列化器(Jackson)

这是最直观的方式,每个枚举对应一个反序列化器,绑定到Obj的对应属性上。

步骤1:定义工厂类(示例)

public class EnumFactory {
    // 根据ID获取EnumA实例
    public static EnumA getEnumAById(int id) {
        // 这里写你的工厂逻辑,比如从缓存/映射表中查找
        return Arrays.stream(EnumA.values())
                .filter(e -> e.getId() == id)
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("Invalid EnumA ID: " + id));
    }

    // 根据ID获取EnumB实例
    public static EnumB getEnumBById(String id) {
        return Arrays.stream(EnumB.values())
                .filter(e -> e.getId().equals(id))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("Invalid EnumB ID: " + id));
    }
}

步骤2:写枚举专属反序列化器

比如EnumA的反序列化器:

public class EnumAJsonDeserializer extends StdDeserializer<EnumA> {
    public EnumAJsonDeserializer() {
        super(EnumA.class);
    }

    @Override
    public EnumA deserialize(JsonParser parser, DeserializationContext context) throws IOException {
        int enumId = parser.getValueAsInt();
        // 调用工厂获取实例,完全避开构造方法
        return EnumFactory.getEnumAById(enumId);
    }
}

EnumB的反序列化器类似,把类型换成EnumB,ID处理改成String即可。

步骤3:在Obj的属性上标注反序列化器

public class Obj {
    // 明确指定用EnumA的反序列化器
    @JsonDeserialize(using = EnumAJsonDeserializer.class)
    private EnumA enumAField;

    // 明确指定用EnumB的反序列化器
    @JsonDeserialize(using = EnumBJsonDeserializer.class)
    private EnumB enumBField;

    // 其他属性、getter/setter
}

这样Jackson在反序列化时,会根据属性上的注解调用对应的反序列化器,进而调用工厂方法获取实例。


方案2:通用反序列化器(Jackson)

如果枚举类型多,不想写N个反序列化器,可以写一个通用的,通过上下文自动推断枚举类型:

public class GenericEnumDeserializer extends StdDeserializer<Object> implements ContextualDeserializer {
    private Class<?> targetEnumType;

    // 无参构造,供框架初始化
    public GenericEnumDeserializer() {
        super(Object.class);
    }

    // 带类型参数的构造,用于上下文初始化
    private GenericEnumDeserializer(Class<?> targetEnumType) {
        super(targetEnumType);
        this.targetEnumType = targetEnumType;
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext context, BeanProperty property) throws JsonMappingException {
        // 获取当前正在反序列化的属性类型(比如EnumA或EnumB)
        Class<?> rawType = property.getType().getRawClass();
        return new GenericEnumDeserializer(rawType);
    }

    @Override
    public Object deserialize(JsonParser parser, DeserializationContext context) throws IOException {
        String idStr = parser.getValueAsString();
        // 根据类型匹配工厂方法
        if (EnumA.class.isAssignableFrom(targetEnumType)) {
            return EnumFactory.getEnumAById(Integer.parseInt(idStr));
        } else if (EnumB.class.isAssignableFrom(targetEnumType)) {
            return EnumFactory.getEnumBById(idStr);
        }
        throw new JsonMappingException(parser, "Unsupported enum type: " + targetEnumType.getName());
    }
}

然后在Obj的所有枚举属性上统一标注:

public class Obj {
    @JsonDeserialize(using = GenericEnumDeserializer.class)
    private EnumA enumAField;

    @JsonDeserialize(using = GenericEnumDeserializer.class)
    private EnumB enumBField;
}

这个方案能减少重复代码,适合枚举类型较多的场景。


方案3:Gson框架下的TypeAdapterFactory

如果用Gson做序列化,思路类似,写一个TypeAdapterFactory来处理所有自定义枚举:

public class EnumTypeAdapterFactory implements TypeAdapterFactory {
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
        Class<T> rawType = (Class<T>) typeToken.getRawType();
        // 只处理继承自AbstractEnum的类型
        if (!AbstractEnum.class.isAssignableFrom(rawType)) {
            return null; // 交给Gson默认处理
        }

        return new TypeAdapter<T>() {
            @Override
            public void write(JsonWriter writer, T value) throws IOException {
                // 序列化时只输出ID
                AbstractEnum enumValue = (AbstractEnum) value;
                writer.value(enumValue.getId());
            }

            @Override
            public T read(JsonReader reader) throws IOException {
                String id = reader.nextString();
                // 根据类型调用对应工厂方法
                if (rawType == EnumA.class) {
                    return (T) EnumFactory.getEnumAById(Integer.parseInt(id));
                } else if (rawType == EnumB.class) {
                    return (T) EnumFactory.getEnumBById(id);
                }
                throw new IOException("Unknown enum type: " + rawType.getName());
            }
        };
    }
}

注册这个Factory到Gson实例:

Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(new EnumTypeAdapterFactory())
        .create();

之后反序列化Obj时,Gson会自动匹配对应的TypeAdapter,调用工厂获取实例。


注意事项

  • 确保工厂类的方法是线程安全的,因为反序列化可能在多线程环境下执行;
  • 如果不同枚举的ID类型不同(比如int和String),要在反序列化器里做好类型转换,避免ClassCastException;
  • 如果Obj的属性用的是父类AbstractEnum类型(而非具体的EnumA/EnumB),那需要额外的上下文信息(比如Obj里的标识字段)来推断具体类型,这时候可以在反序列化时先读取标识字段,再调用对应工厂方法。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 03:35:52