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

如何在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. 逻辑验证

反序列化流程如下:

  1. 首先调用setMachineType,将Deployment层级的machineType存入注入上下文;
  2. 调用setMachines时,使用带有注入上下文的ObjectMapper反序列化MachineSet;
  3. 处理每个machine节点时,Jackson从注入上下文获取machineType,结合@JsonTypeInfo配置自动实例化对应的AwsMachine或OpenStackMachine;
  4. 最后通过withDefaultXXX方法填充默认值,完成机器对象初始化。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 08:34:56