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

基于字段存在性与字段值的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;
    }
}

关键细节说明

  1. 避免StackOverflowError:在自定义CallbackTypeResolver的typeFromId方法中,读取完JSON节点后必须重置parser的token位置,否则后续Jackson解析会进入循环递归,导致栈溢出。
  2. 解决InvalidTypeIdException:自定义解析器直接根据字段存在性映射类型,而非依赖固定的类型ID字段,避免了类型ID不匹配的问题。
  3. 覆盖默认@JsonTypeInfo:通过@JsonTypeIdResolver指定自定义解析器,完全接管第一层类型识别逻辑,不受默认注解规则限制。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 01:37:01