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

Jackson:仅当JSON字段值匹配类属性值时才允许反序列化

解决Jackson反序列化时类型字段不匹配的问题

问题背景

现有抽象类Event及其子类EventA、EventB的定义如下:

public abstract class Event {
    private final String eventId;

    public Event(String eventId) {
        this.eventId = eventId;
    }
}

public class EventA extends Event {
    private final String type = "eventA";

    @JsonCreator
    public EventA(@JsonProperty("eventId") String eventId) {
        super(eventId);
    }

    public String getType() {
        return type;
    }
}

public class EventB extends Event {
    private final String type = "eventB";

    @JsonCreator
    public EventB(@JsonProperty("eventId") String eventId) {
        super(eventId);
    }

    public String getType() {
        return type;
    }
}

当前反序列化存在漏洞:JSON中的type字段不会与目标子类的默认type值校验,导致可以将含type:"eventA"的JSON反序列化为EventB实例,最终得到type属性为eventA的EventB对象:

EventA eventA = new ObjectMapper().readValue(
    "{\"eventId\":\"foo\", \"type\": \"eventA\"}",
    EventA.class);

// 此处会生成type为"eventA"的EventB对象
EventB eventB = new ObjectMapper().readValue(
    "{\"eventId\":\"foo\", \"type\": \"eventA\"}",
    EventB.class);

需要实现:反序列化时校验JSON中type字段值与目标类的type默认值是否一致,不匹配则抛出异常。


解决方案

方案1:在构造方法中显式校验

修改子类的@JsonCreator构造方法,接收JSON中的type字段并与类的默认值对比,不匹配则抛出非法参数异常:

public class EventA extends Event {
    private final String type = "eventA";

    @JsonCreator
    public EventA(
        @JsonProperty("eventId") String eventId,
        @JsonProperty("type") String type
    ) {
        super(eventId);
        if (!this.type.equals(type)) {
            throw new IllegalArgumentException(
                String.format("Invalid type for EventA: expected '%s', got '%s'", this.type, type)
            );
        }
    }

    public String getType() {
        return type;
    }
}

public class EventB extends Event {
    private final String type = "eventB";

    @JsonCreator
    public EventB(
        @JsonProperty("eventId") String eventId,
        @JsonProperty("type") String type
    ) {
        super(eventId);
        if (!this.type.equals(type)) {
            throw new IllegalArgumentException(
                String.format("Invalid type for EventB: expected '%s', got '%s'", this.type, type)
            );
        }
    }

    public String getType() {
        return type;
    }
}

此方案直接在对象构造阶段完成校验,逻辑简单直观,不依赖额外组件。

方案2:自定义通用反序列化器

若不想修改每个子类的构造方法,可编写通用的Event子类反序列化器,统一处理类型校验:

  1. 实现自定义反序列化器:
public class EventDeserializer<T extends Event> extends StdDeserializer<T> {
    private final Class<T> eventClass;
    private final String expectedType;

    public EventDeserializer(Class<T> eventClass, String expectedType) {
        super(eventClass);
        this.eventClass = eventClass;
        this.expectedType = expectedType;
    }

    @Override
    public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        JsonNode node = p.getCodec().readTree(p);
        String typeFromJson = node.get("type").asText();
        
        if (!expectedType.equals(typeFromJson)) {
            throw ctxt.weirdStringException(typeFromJson, String.class,
                String.format("Invalid type for %s: expected '%s'", eventClass.getSimpleName(), expectedType));
        }
        
        // 复用默认反序列化逻辑创建实例
        return p.getCodec().treeToValue(node, eventClass);
    }
}
  1. 在子类上注册该反序列化器:
@JsonDeserialize(using = EventDeserializer.class)
public class EventA extends Event {
    private final String type = "eventA";

    @JsonCreator
    public EventA(@JsonProperty("eventId") String eventId) {
        super(eventId);
    }

    public String getType() {
        return type;
    }
}

注:可以通过反射读取子类的type字段值自动填充expectedType,避免硬编码;也可自定义注解传递预期类型值。

方案3:使用Jackson多态反序列化(推荐)

如果业务需要根据type字段自动匹配子类,可使用Jackson的多态类型处理机制,既实现自动类型匹配,又能避免类型不匹配问题:

  1. 在抽象类Event上添加多态注解:
@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "type"
)
@JsonSubTypes({
    @JsonSubTypes.Type(value = EventA.class, name = "eventA"),
    @JsonSubTypes.Type(value = EventB.class, name = "eventB")
})
public abstract class Event {
    private final String eventId;

    public Event(String eventId) {
        this.eventId = eventId;
    }
}
  1. 反序列化时直接指定父类Event类型,Jackson会自动根据type字段选择对应子类;若强制指定错误子类(如用type:"eventA"反序列化为EventB),会直接抛出MismatchedInputException:
// 自动反序列化为EventA实例
Event event = new ObjectMapper().readValue(
    "{\"eventId\":\"foo\", \"type\": \"eventA\"}",
    Event.class);

// 此处会抛出MismatchedInputException,类型不匹配
EventB eventB = new ObjectMapper().readValue(
    "{\"eventId\":\"foo\", \"type\": \"eventA\"}",
    EventB.class);

此方案符合Jackson的最佳实践,同时解决了类型校验和多态反序列化的需求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 14:35:55