RepositoryRestController处理多HAL URI POST请求的反序列化异常问题
批量创建用户时Spring HATEOAS CollectionModel反序列化异常解决
场景与问题
User与EmploymentType为ManyToOne关联,与Skill为OneToMany关联,通过自定义RepositoryRestController在持久化前做额外校验。单个创建用户并关联EmploymentType的请求正常,但批量创建包含多个Skill HAL URI的用户时,出现JSON反序列化错误,无法将数组转换为CollectionModel。
相关代码
User实体类
@Entity public class User { //.. private String name; @ManyToOne private EmploymentType employmentType; @OneToMany(mappedBy = "user") private Set<Skill> skills = new HashSet<>(); //.. }
Skill实体类
@Entity public class Skill { //.. @ManyToOne private User user; //.. }
EmploymentType实体类
@Entity public class EmploymentType { //.. @ManyToOne private User user; //.. }
自定义RepositoryRestController
@RepositoryRestController public class CustomUserController { private UserRepository userRepository; @PostMapping(path = "/users/createUserWithEmploymentType") public @ResponseBody String createUserWithEmploymentType(@RequestBody EntityModel<User> users) { System.out.println(users); // .... return "SUCCESS"; } @PostMapping(path = "/users/createUserWithSkillBatch") public @ResponseBody String createUserWithSkill(@RequestBody CollectionModel<EntityModel<User>> users) { System.out.println(users); // .... return "SUCCESS"; } }
报错信息
...ExceptionHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type org.springframework.hateoas.CollectionModel from Array value (token JsonToken.START_ARRAY); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type org.springframework.hateoas.CollectionModel from Array value (token JsonToken.START_ARRAY)<EOL> at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 1]]
解决方案
核心原因
Spring HATEOAS的CollectionModel是HAL规范的包裹式结构,它默认期望的JSON格式是包含_embedded和_links的对象,而非直接的数组。如果请求体传的是纯数组,Jackson无法自动完成反序列化。
方法1:调整请求体为HAL标准格式
把请求的JSON从纯数组改成CollectionModel要求的HAL结构,示例:
{ "_embedded": { "users": [ { "name": "张三", "skills": [ {"_links": {"self": {"href": "/skills/1"}}}, {"_links": {"self": {"href": "/skills/2"}}} ], "_links": {"self": {"href": "/users/1"}} }, { "name": "李四", "skills": [ {"_links": {"self": {"href": "/skills/3"}}} ], "_links": {"self": {"href": "/users/2"}} } ] }, "_links": { "self": {"href": "/users"} } }
方法2:修改Controller参数类型,手动转换
如果无法调整请求体格式,可将方法参数改为List<EntityModel<User>>,然后在方法内部手动包装成CollectionModel:
@PostMapping(path = "/users/createUserWithSkillBatch") public @ResponseBody String createUserWithSkill(@RequestBody List<EntityModel<User>> userList) { // 手动把列表包装成CollectionModel CollectionModel<EntityModel<User>> users = CollectionModel.of(userList); System.out.println(users); // 执行自定义校验和持久化逻辑 return "SUCCESS"; }
方法3:配置Jackson支持数组转CollectionModel
通过自定义Jackson模块,让框架能自动将数组反序列化为CollectionModel,创建以下配置类:
@Configuration public class HateoasConfig { @Bean public Module collectionModelArrayDeserializerModule() { SimpleModule module = new SimpleModule(); module.addDeserializer(CollectionModel.class, new JsonDeserializer<CollectionModel<?>>() { @Override public CollectionModel<?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { JsonNode arrayNode = p.getCodec().readTree(p); ObjectMapper mapper = (ObjectMapper) p.getCodec(); if (arrayNode.isArray()) { // 处理数组格式,转换为CollectionModel List<Object> content = mapper.readValue(arrayNode.traverse(), new TypeReference<List<Object>>() {}); return CollectionModel.of(content); } // 兼容标准HAL格式 return mapper.readValue(arrayNode.traverse(), CollectionModel.class); } }); return module; } }
内容的提问来源于stack exchange,提问作者Kiran Kyle
相关产品推荐
相关产品推荐

