如何在Jackson的@JsonAnySetter中提供多态反序列化类型信息?
问题描述
现有一个机器集群部署的YAML配置文件,结构如下:
deployment: imageName: generalImageToUse machineType: openstack/aws flavor: generalFlavorToUse machines: machine1: hostName: machine-1 flavor: flavorOverride machine2: hostName: machine-2 imageName: imageNameOverride
对应的Java反序列化类结构如下:
Deployment类
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) class Deployment { private String imageName; private MachineType machineType; private String flavor; private MachineSet machines; public class MachineSet { private final List<Machine> machines = new ArrayList<>(); @JsonAnySetter public void setMachine(String nameInYaml, Machine machine) { machines.add(machine.withDefaultImageName(getImageName()) .withDefaultFlavor(getFlavor()) .withDefaultType(getMachineType())); } public List<Machine> getMachines() { return machines; } } }
Machine抽象类
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "machineType") @JsonSubTypes({ @JsonSubTypes.Type(value = AwsMachine.class, name = "aws"), @JsonSubTypes.Type(value = OpenStackMachine.class, name = "openstack") }) public abstract class Machine { private String hostName; private String flavor; private MachineType machineType; private String imageName; public abstract String getDefaultFqdn(); public String getHost() { return String.format("%s.%s", hostName, getDefaultFqdn()); } }
OpenStackMachine实现类
public class OpenStackMachine extends Machine { public final static String DEFAULT_FQDN = "openstack.domain.name"; @Override public String getDefaultFqdn() { return DEFAULT_FQDN; } }
AwsMachine实现类
public class AwsMachine extends Machine { public final static String DEFAULT_FQDN = "aws.domain.name"; @Override public String getDefaultFqdn() { return DEFAULT_FQDN; } }
当前核心问题:反序列化时,@JsonAnySetter标注的setMachine方法被调用前,Jackson需要实例化Machine子类,但决定类型的machineType信息仅存在于Deployment层级(不在单个machine节点中),导致无法自动确定实例化类型,进而反序列化失败。要求不自定义反序列化器、不在@JsonAnySetter内构造新对象的前提下解决该问题。
解决方案:利用Jackson的
@JsonInject注入父级上下文信息 可以通过@JsonInject将Deployment层级的machineType注入到Machine的反序列化上下文,让Jackson获取到类型信息来实例化正确的子类,具体步骤如下:
1. 修改Machine类,添加@JsonInject标记类型字段
调整Machine类的machineType字段,用@JsonInject标记该字段需要从上下文注入,同时调整@JsonTypeInfo配置确保类型字段可见:
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "machineType", visible = true) // 保留类型字段,不被Jackson自动移除 @JsonSubTypes({ @JsonSubTypes.Type(value = AwsMachine.class, name = "aws"), @JsonSubTypes.Type(value = OpenStackMachine.class, name = "openstack") }) public abstract class Machine { private String hostName; private String flavor; @JsonInject // 标记该字段需从上下文注入 private MachineType machineType; private String imageName; public abstract String getDefaultFqdn(); public String getHost() { return String.format("%s.%s", hostName, getDefaultFqdn()); } }
2. 在Deployment类中传递注入上下文
修改Deployment类,捕获machineType并将其放入Jackson的注入上下文,再传递给MachineSet的反序列化过程:
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) class Deployment { private String imageName; private MachineType machineType; private String flavor; private MachineSet machines; // 存储注入上下文,用于传递给Machine的反序列化 private final InjectableValues.Std injectableValues = new InjectableValues.Std(); @JsonSetter public void setMachineType(MachineType machineType) { this.machineType = machineType; // 将machineType存入注入上下文,key需和Machine类中@JsonInject的字段名一致 injectableValues.addValue("machineType", machineType); } // 自定义machines字段的反序列化,传入注入上下文 @JsonSetter public void setMachines(JsonNode machinesNode) throws IOException { ObjectMapper mapper = new ObjectMapper(); mapper.setInjectableValues(injectableValues); this.machines = mapper.treeToValue(machinesNode, MachineSet.class); } public class MachineSet { private final List<Machine> machines = new ArrayList<>(); @JsonAnySetter public void setMachine(String nameInYaml, Machine machine) { machines.add(machine.withDefaultImageName(getImageName()) .withDefaultFlavor(getFlavor()) .withDefaultType(getMachineType())); } public List<Machine> getMachines() { return machines; } } }
3. 逻辑验证
反序列化流程如下:
- 首先调用
setMachineType,将Deployment层级的machineType存入注入上下文; - 调用
setMachines时,使用带有注入上下文的ObjectMapper反序列化MachineSet; - 处理每个machine节点时,Jackson从注入上下文获取
machineType,结合@JsonTypeInfo配置自动实例化对应的AwsMachine或OpenStackMachine; - 最后通过
withDefaultXXX方法填充默认值,完成机器对象初始化。
内容的提问来源于stack exchange,提问作者RekaB
相关产品推荐
相关产品推荐

