无类型信息下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
相关产品推荐
相关产品推荐

