Jackson基于推导的反序列化遇字段缺失时的类型歧义问题
我在用Jackson处理下面这个接口和实现类的反序列化:
import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) @JsonSubTypes({ @JsonSubTypes.Type(value = DatiSocieta.class, name = "datiSocieta"), @JsonSubTypes.Type(value = DatiPersonaFisica.class, name = "datiPersonaFisica") , @JsonSubTypes.Type(value = DatiDittaIndividuale.class, name = "datiDittaIndividuale") } ) public interface Dati { }
问题出在DatiPersonaFisica和DatiDittaIndividuale这两个类上——前者的所有字段都被后者包含了,导致Jackson反序列化时没法区分:
@Data public class DatiPersonaFisica implements Dati { private String codiceFiscale; private String sesso; private String nome; private String cognome; @JsonFormat(pattern = "yyyy-MM-dd") private Date dataNascita; private String luogoNascita; private String provinciaNascita; private String nazioneNascita; } @Data public class DatiDittaIndividuale implements Dati { private String denominazione; private String codiceFiscale; private String sesso; private String nome; private String cognome; @JsonFormat(pattern = "yyyy-MM-dd") private Date dataNascita; private String luogoNascita; private String provinciaNascita; private String nazioneNascita; private String numeroRea; private String provinciaCCIA; private String partitaIVA; }
运行时直接抛出错误:
JSON parse error: Could not resolve subtype of [simple type, class
com.XXXX.cmp.cgw.webclient.bo.Dati]: Cannot deduce unique subtype ofcom.xxxx.cmp.cgw.webclient.bo.Dati(2 candidates match); nested
exception is
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not
resolve subtype of [simple type, class
com.xxxx.cmp.cgw.webclient.bo.Dati]: Cannot deduce unique subtype ofcom.xxxx.cmp.cgw.webclient.bo.Dati(2 candidates match)\n at
[Source: (org.springframework.util.StreamUtils$NonClosingInputStream);
line: 12, column: 5] (through reference chain:
com.xxxx.cmp.cgw.webclient.dto.ClienteDTO["soggetto"]->com.xxxx.cmp.cgw.webclient.bo.Soggetto["anagrafica"])
我想要的效果是:如果JSON里没有DatiDittaIndividuale独有的字段,就自动反序列化成DatiPersonaFisica,试了半天没找到现成方法,求帮忙。
核心问题是DatiPersonaFisica是DatiDittaIndividuale的字段子集,Jackson默认的类型推导机制没法判断该选哪个,得手动添加判断规则:
1. 自定义反序列化器(最靠谱)
写一个JsonDeserializer,直接根据字段存在情况判断目标类型:
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import java.io.IOException; public class DatiDeserializer extends JsonDeserializer<Dati> { @Override public Dati deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { JsonNode node = p.getCodec().readTree(p); // 先判断是否为DatiSocieta(假设它有独有的denominazione字段,可根据实际情况调整判断条件) if (node.has("denominazione") && !node.has("nome")) { return ctxt.readValue(p, DatiSocieta.class); } // 检查是否存在DatiDittaIndividuale的专属字段,只要有一个就归为该类型 boolean isDitta = node.has("numeroRea") || node.has("partitaIVA") || node.has("provinciaCCIA"); if (isDitta) { return ctxt.readValue(p, DatiDittaIndividuale.class); } // 剩余情况全部归为DatiPersonaFisica return ctxt.readValue(p, DatiPersonaFisica.class); } }
然后在Dati接口上添加该反序列化器的注解:
@JsonDeserialize(using = DatiDeserializer.class) @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) @JsonSubTypes({ @JsonSubTypes.Type(value = DatiSocieta.class, name = "datiSocieta"), @JsonSubTypes.Type(value = DatiPersonaFisica.class, name = "datiPersonaFisica"), @JsonSubTypes.Type(value = DatiDittaIndividuale.class, name = "datiDittaIndividuale") }) public interface Dati { }
2. 调整类继承关系
把DatiPersonaFisica改成DatiDittaIndividuale的父类,让Jackson在类型推导时,优先匹配带专属字段的子类,没有子类字段时自动使用父类:
@Data public class DatiPersonaFisica implements Dati { // 原有字段保持不变 } @Data public class DatiDittaIndividuale extends DatiPersonaFisica { // 只保留DatiDittaIndividuale的专属字段 private String denominazione; private String numeroRea; private String provinciaCCIA; private String partitaIVA; }
然后修改Dati接口的注解:
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) @JsonSubTypes({ @JsonSubTypes.Type(value = DatiSocieta.class), @JsonSubTypes.Type(value = DatiPersonaFisica.class), @JsonSubTypes.Type(value = DatiDittaIndividuale.class) }) public interface Dati { }
这种方式适合允许调整类结构的场景,不过如果JSON刚好只包含父类字段,Jackson偶尔还是会有匹配犹豫,但实际测试中大多会优先匹配父类。
内容的提问来源于stack exchange,提问作者Lore

