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子类反序列化器,统一处理类型校验:
- 实现自定义反序列化器:
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); } }
- 在子类上注册该反序列化器:
@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的多态类型处理机制,既实现自动类型匹配,又能避免类型不匹配问题:
- 在抽象类
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; } }
- 反序列化时直接指定父类
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
相关产品推荐
相关产品推荐

