使用Jackson注解@JsonTypeInfo与@JsonSubTypes时遇解码异常求助
问题:Jackson反序列化时无法解析类型ID '4'
错误信息
org.springframework.core.codec.DecodingException: JSON decoding error: Could not resolve type id '4' as a subtype of com.myproject.onetoone.viewmodels.request.TopicGenerationRequest: known type ids = [GoalsGenerationRequest, TodoGenerationRequest, TopicGenerationRequest]; nested exception is com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id '4' as a subtype of com.myproject.onetoone.viewmodels.request.TopicGenerationRequest: known type ids = [GoalsGenerationRequest, TodoGenerationRequest, TopicGenerationRequest] at [Source: (io.netty.buffer.ByteBufInputStream); line: 2, column: 13]
相关代码
父类 TopicGenerationRequest
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes({ @Type(value = GoalsGenerationRequest.class, name="GoalsGenerationRequest"), @Type(value = TodoGenerationRequest.class, name="TodoGenerationRequest") }) @Getter @Setter @AllArgsConstructor @NoArgsConstructor public class TopicGenerationRequest { private int type; }
子类 GoalsGenerationRequest
import com.myproject.onetoone.viewmodels.request.TopicGenerationRequest; import com.fasterxml.jackson.annotation.JsonTypeName; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import java.util.List; @JsonTypeName("GoalsGenerationRequest") @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class GoalsGenerationRequest extends TopicGenerationRequest { private List<Goal> goals; }
子类 TodoGenerationRequest
@JsonTypeName("TodoGenerationRequest") @Getter @Setter public class TodoGenerationRequest extends TopicGenerationRequest { @NotNull(message = "A valid UUID must be passed") private UUID ownerId; @NotBlank(message = "valid title should be added") private String title; private ZonedDateTime deadline; }
Goal 类
@Getter @Setter @NoArgsConstructor @AllArgsConstructor public class Goal { private int type; private int id; }
请求内容(Postman发送)
{ "type": 4, "goals": [ { "type": 3, "id": 456 }, { "type": 3, "id": 454 } ] }
端点映射逻辑
Mono<ProfileIdentity> profileIdentityMono = Utility.getUserPrincipal().map(UserPrincipal::getProfileID); Mono<TopicGenerationRequest> generationRequest = serverRequest.bodyToMono(TopicGenerationRequest.class) .onErrorResume(castError -> Mono .error(new ClientErrorException(ErrorCodes.INVALID_REQUEST, "please provide valid json body")));
解决方案
核心问题
当前配置存在两个冲突:
@JsonTypeInfo指定用字符串名称作为类型ID,但请求中传递的是数字4,Jackson无法将数字匹配到GoalsGenerationRequest这类字符串名称- 父类的
type字段与Jackson用于类型识别的type属性重名,导致Jackson把数字4当成类型名称解析,而非普通字段
具体修改方案
方案一:用数字作为类型ID匹配子类
如果希望继续使用数字type区分子类,调整注解配置:
- 修改父类的
@JsonSubTypes,将name设置为对应数字:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes({ @Type(value = GoalsGenerationRequest.class, name="4"), // 对应请求的type=4 @Type(value = TodoGenerationRequest.class, name="5") // 按需设置Todo对应的数字 }) @Getter @Setter @AllArgsConstructor @NoArgsConstructor public class TopicGenerationRequest { private int type; }
- 子类的
@JsonTypeName同步为对应数字(或直接删除,依赖父类配置):
@JsonTypeName("4") @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class GoalsGenerationRequest extends TopicGenerationRequest { private List<Goal> goals; }
方案二:用字符串作为类型ID(规范做法)
遵循Jackson默认的名称匹配逻辑,调整请求和配置:
- 修改Postman请求的
type值为字符串:
{ "type": "GoalsGenerationRequest", "goals": [ { "type": 3, "id": 456 }, { "type": 3, "id": 454 } ] }
- 可选:修改父类的
type字段名称,避免与Jackson类型识别属性混淆:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes({ @Type(value = GoalsGenerationRequest.class, name="GoalsGenerationRequest"), @Type(value = TodoGenerationRequest.class, name="TodoGenerationRequest") }) @Getter @Setter @AllArgsConstructor @NoArgsConstructor public class TopicGenerationRequest { private int requestType; // 替换原type字段名,消除冲突 }
注意事项
- 无论哪种方案,请求中的
type值必须与Jackson配置的类型ID完全匹配 - 若保留父类
type字段与Jackson类型属性同名,Jackson会优先将其解析为类型ID,而非普通业务字段
内容的提问来源于stack exchange,提问作者Deepak007
相关产品推荐
相关产品推荐

