基于字段存在性与字段值的Jackson JSON反序列化问题
解决方案:分层自定义类型解析实现Jackson反序列化
针对你的多层级抽象类反序列化需求,可以通过分层配置Jackson类型解析逻辑来实现:先通过job/notify字段区分抽象子类,再通过object.type区分具体子类,完全规避你遇到的三种问题。
步骤1:根抽象类Callback配置第一层类型识别
自定义TypeIdResolver来判断JSON中是否存在job或notify字段,映射到对应的抽象子类JobCallback/NotificationCallback:
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.DatabindContext; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonParser; import com.fasterxml.jackson.databind.jsontype.TypeIdResolverBase; import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; @JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, include = JsonTypeInfo.As.EXISTING_PROPERTY) @JsonTypeIdResolver(CallbackTypeResolver.class) public abstract class Callback { // 公共字段/方法,比如公共ID等 } // 自定义类型解析器,处理第一层抽象子类的判断 class CallbackTypeResolver extends TypeIdResolverBase { private JavaType baseType; @Override public void init(JavaType baseType) { this.baseType = baseType; } @Override public String idFromValue(Object value) { // 序列化时返回对应类型标识 return value instanceof JobCallback ? "job" : "notify"; } @Override public String idFromValueAndType(Object value, Class<?> suggestedType) { return idFromValue(value); } @Override public JavaType typeFromId(DatabindContext context, String id) throws IOException { // 反序列化时,读取JSON根节点判断字段存在性 JsonParser parser = context.getParser(); JsonNode rootNode = parser.readValueAsTree(); // 重置parser位置,避免后续解析异常 parser.setCurrentToken(rootNode.asToken()); parser.nextToken(); if (rootNode.has("job")) { return context.getTypeFactory().constructSpecializedType(baseType, JobCallback.class); } else if (rootNode.has("notify")) { return context.getTypeFactory().constructSpecializedType(baseType, NotificationCallback.class); } throw new IllegalArgumentException("无法识别的Callback类型:缺少job/notify字段"); } @Override public JsonTypeInfo.Id getMechanism() { return JsonTypeInfo.Id.CUSTOM; } }
步骤2:抽象子类配置第二层具体类型识别
在JobCallback和NotificationCallback上,基于object.type字段配置类型映射,区分具体子类:
import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; // JobCallback抽象类:基于object.type映射具体子类 @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "object.type", visible = true) @JsonSubTypes({ @JsonSubTypes.Type(value = DomainJobCallback.class, name = "domain"), @JsonSubTypes.Type(value = TransferJobCallback.class, name = "transfer") }) public abstract class JobCallback extends Callback { private ObjectNode object; // 或自定义Object实体类,包含type字段 // getter/setter public ObjectNode getObject() { return object; } public void setObject(ObjectNode object) { this.object = object; } } // NotificationCallback抽象类:同理配置 @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "object.type", visible = true) @JsonSubTypes({ @JsonSubTypes.Type(value = DomainNotificationCallback.class, name = "domain"), @JsonSubTypes.Type(value = TransferNotificationCallback.class, name = "transfer") }) public abstract class NotificationCallback extends Callback { private ObjectNode object; // getter/setter public ObjectNode getObject() { return object; } public void setObject(ObjectNode object) { this.object = object; } }
步骤3:具体子类实现
无需额外注解,直接继承对应抽象类即可:
// 具体Job子类 public class DomainJobCallback extends JobCallback { // 自定义字段,比如domainId等 private String domainId; // getter/setter public String getDomainId() { return domainId; } public void setDomainId(String domainId) { this.domainId = domainId; } } public class TransferJobCallback extends JobCallback { private String transferUrl; // getter/setter public String getTransferUrl() { return transferUrl; } public void setTransferUrl(String transferUrl) { this.transferUrl = transferUrl; } } // 具体Notification子类 public class DomainNotificationCallback extends NotificationCallback { private String domainMessage; // getter/setter public String getDomainMessage() { return domainMessage; } public void setDomainMessage(String domainMessage) { this.domainMessage = domainMessage; } } public class TransferNotificationCallback extends NotificationCallback { private String transferStatus; // getter/setter public String getTransferStatus() { return transferStatus; } public void setTransferStatus(String transferStatus) { this.transferStatus = transferStatus; } }
关键细节说明
- 避免StackOverflowError:在自定义
CallbackTypeResolver的typeFromId方法中,读取完JSON节点后必须重置parser的token位置,否则后续Jackson解析会进入循环递归,导致栈溢出。 - 解决InvalidTypeIdException:自定义解析器直接根据字段存在性映射类型,而非依赖固定的类型ID字段,避免了类型ID不匹配的问题。
- 覆盖默认@JsonTypeInfo:通过
@JsonTypeIdResolver指定自定义解析器,完全接管第一层类型识别逻辑,不受默认注解规则限制。
内容的提问来源于stack exchange,提问作者Saif
相关产品推荐
相关产品推荐

