Jackson Subtypes多态反序列化问题:AuthInfo解析始终为null
从第三方API获取两种JSON结构,二者差异在于authMode对应的子节点(如authMode为PAN时对应panInfo,为custid时对应custidInfo)。希望借助Jackson Subtypes特性实现更简洁的多态反序列化,当前使用Spring Boot 3.3.2 + Jackson 2.17.2环境,反序列化第一种JSON时,MandateAuthDtls中的AuthInfo始终为null,期望能正确解析为PanInfo类型。请问是否可通过Jackson Subtypes解决该场景?若不可行,有何更优方案?
JSON示例
第一种类型
{ "mandateAuthDtls": { "transactionID": "<Transaction ID>", "mndtType": "< AMEND / CANCEL / SUSPEND/ REVOKE /CUSTOM_CANCEL>", "mandateRequestDtl": { "MandateReqDoc": "<Encrypted and Signed request XML>" }, /*SUBCLASS INFORMATION STARTS HERE*/ "authMode": "PAN", "panInfo": { "pan": "<Encrypted PAN>" } } }
第二种类型
{ "mandateAuthDtls": { "transactionID": "<Transaction ID>", "mndtType": "<AMEND / CANCEL / SUSPEND/ REVOKE /CUSTOM_CANCEL>", "mandateRequestDtl": { "MandateReqDoc": "<Encrypted and Signed request XML>" }, /*SUBCLASS INFORMATION STARTS HERE*/ "authMode": "custid", "custidInfo": { "custid": "<Encrypted custid>" } } }
当前代码
顶级类MandateAuthDtls
@Data @Builder @Jacksonized @JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) public class MandateAuthDtls { @JsonProperty("transactionID") private String transactionID; @JsonProperty("mandateType") private String mandateType; @JsonProperty("mandateRequestDtl") private MandateRequestDtl mandateRequestDtl; @JsonProperty("authMode") private String authMode; //@JsonProperty private AuthInfo authInfo; }
抽象类AuthInfo
@JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "authMode", defaultImpl = CardInfo.class, visible = true ) @JsonSubTypes({ @JsonSubTypes.Type(value = AadharInfo.class, name = "Aadhaar"), @JsonSubTypes.Type(value = CustIDInfo.class, name = "custid"), @JsonSubTypes.Type(value = PanInfo.class, name = "PAN") }) public abstract class AuthInfo { }
子类PanInfo
@JsonTypeName("PAN") public class PanInfo extends AuthInfo { @JsonProperty("Pan") private String pan; public String getPan() { return pan; } public void setPan(String pan) { this.pan = pan; } }
完全可以通过Jackson Subtypes解决该问题,当前authInfo为null是因为存在几处关键配置错误,修正步骤如下:
1. 修正MandateAuthDtls中authInfo的字段映射
JSON中对应AuthInfo的节点是panInfo/custidInfo,但当前authInfo字段未指定JsonProperty,且没有配置多别名支持。需要给authInfo添加@JsonProperty和@JsonAlias,覆盖所有可能的节点名:
@JsonProperty @JsonAlias({"panInfo", "custidInfo", "aadhaarInfo"}) private AuthInfo authInfo;
2. 调整AuthInfo的类型信息配置
当前@JsonTypeInfo的include = JsonTypeInfo.As.EXTERNAL_PROPERTY配置是正确的(因为authMode和authInfo处于同一层级),但需注意:
- 确保
authMode的取值与@JsonSubTypes中的name完全匹配(比如PAN和custid大小写一致) - 若没有默认实现需求,可移除
defaultImpl = CardInfo.class,避免引入不必要的依赖
3. 修正子类的字段名匹配
PanInfo中的@JsonProperty("Pan")与JSON里的pan大小写不一致,虽然开启了大小写不敏感,但建议保持一致避免潜在问题:
@JsonProperty("pan") private String pan;
4. 验证Builder兼容性
使用@Jacksonized和@Builder时,需确保生成的Builder包含authInfo的设置方法,避免反序列化时无法赋值。可检查编译后的代码确认Builder是否正确生成该字段的操作方法。
最终修正后的核心代码示例
MandateAuthDtls调整后
@Data @Builder @Jacksonized @JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) public class MandateAuthDtls { @JsonProperty("transactionID") private String transactionID; @JsonProperty("mandateType") private String mandateType; @JsonProperty("mandateRequestDtl") private MandateRequestDtl mandateRequestDtl; @JsonProperty("authMode") private String authMode; @JsonProperty @JsonAlias({"panInfo", "custidInfo", "aadhaarInfo"}) private AuthInfo authInfo; }
PanInfo调整后
@JsonTypeName("PAN") public class PanInfo extends AuthInfo { @JsonProperty("pan") private String pan; // getter和setter保持不变 }
替代方案(若Subtypes不适用)
如果第三方JSON结构过于灵活,Subtypes配置复杂,可选择以下方案:
@JsonAnySetter动态处理:在MandateAuthDtls中添加方法接收未匹配的属性,根据authMode手动实例化对应的AuthInfo子类- 自定义反序列化器:继承
StdDeserializer<MandateAuthDtls>,在deserialize方法中手动解析authMode和对应子节点,构建AuthInfo实例
内容的提问来源于stack exchange,提问作者evilo

