Jersey 2.32 RESTful映射遇空对象或MismatchedInputException问题
场景概述
前端通过VueJS表单构建JSON数据,后端使用Jersey 2.32接收并尝试映射解析,但出现参数为null或反序列化异常的问题,需要正确获取数据用于文件上传模块。
前端发送的JSON结构
{"objetDesLiensDuTheme": {"id": 7195,"formalisation": {"selectTitreFichier": "fiche de formalisation","inputTitreFichier": "fiche de formalisation titre fichier","inputFichier": {},},"modop": {"selectTitreFichier": "fiche méthodologique","inputTitreFichier": "fiche méthodologique titre fichier","inputFichier": {},},}}
注:上述为字符串化后的结构,实际
inputFichier是浏览器File对象,console.log输出:inputFichier: File { name: "Fichier formalisation.txt", lastModified: 1690373320586, size: 0, type: "text/plain", webkitRelativePath: ""}
后端代码实现
REST控制器方法
@POST @Path("/create") @Consumes(MediaType.APPLICATION_JSON) public Response createLien(@BeanParam LiensDTO objetDesLiensDuTheme) { Response response; //TODO System.out.println(objetDesLiensDuTheme); response = Response.status(Status.CREATED).build(); return response; }
控制台输出显示参数为null:
LiensDTO [objetDesLiensDuTheme=null]
DTO类定义
LiensDTO.java
public class LiensDTO implements Serializable { private static final long serialVersionUID = -5717326975468155988L; public LiensDTO() { super(); } @FormParam("objetDesLiensDuTheme") // @JsonProperty("objetDesLiensDuTheme") private DocLienDTO objetDesLiensDuTheme; @Override public String toString() { return "LiensDTO [objetDesLiensDuTheme=" + objetDesLiensDuTheme + "]"; } // 省略getter/setter }
DocLienDTO.java
public class DocLienDTO implements Serializable { private static final long serialVersionUID = -493282593738066056L; private int id; private DocumentDTO formalisation; private DocumentDTO modop; // 省略getter/setter }
DocumentDTO.java
public class DocumentDTO implements Serializable { private static final long serialVersionUID = -6698584238714969958L; private String selectTitreFichier; private String inputTitreFichier; private File inputFichier; // 省略getter/setter }
额外异常信息
移除@BeanParam或改用@RequestBody时,抛出反序列化异常:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of api.bean.LiensDTO out of START_ARRAY token at [Source: (org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream); line: 1, column: 1].
实际发送的JSON并非数组结构,也曾尝试用@JsonProperty("objetDesLiensDuTheme")替换@FormParam,问题未解决。
解决方案
1. 修正DTO的JSON映射逻辑
当前JSON根节点是objetDesLiensDuTheme,但LiensDTO将其作为内部属性,导致Jackson无法正确映射,可通过两种方式调整:
方式一:简化DTO结构
直接将DocLienDTO作为请求参数,因为JSON的核心内容就是DocLienDTO的结构,外层包装objetDesLiensDuTheme属于冗余层级。修改控制器方法:@POST @Path("/create") @Consumes(MediaType.APPLICATION_JSON) public Response createLien(DocLienDTO objetDesLiensDuTheme) { System.out.println(objetDesLiensDuTheme); return Response.status(Status.CREATED).build(); }同时可删除
LiensDTO,或根据业务需求保留。方式二:保留LiensDTO并开启根节点解析
移除@FormParam(该注解用于表单参数,不适用JSON场景),改用@JsonProperty,同时配置Jackson支持根节点映射:public class LiensDTO implements Serializable { // ... @JsonProperty("objetDesLiensDuTheme") private DocLienDTO objetDesLiensDuTheme; // ... }添加Jersey的Jackson配置类,启用根节点解析特性:
@Provider public class JacksonConfig implements ContextResolver<ObjectMapper> { private final ObjectMapper mapper; public JacksonConfig() { mapper = new ObjectMapper(); mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE); mapper.enable(SerializationFeature.WRAP_ROOT_VALUE); } @Override public ObjectMapper getContext(Class<?> type) { return mapper; } }
2. 解决File对象的序列化问题
浏览器File对象无法直接序列化为JSON,从字符串化的JSON可见inputFichier被转为空对象{},需调整前端提交方式:
- 改用
multipart/form-data格式提交,Vue中使用FormData构建请求:const formData = new FormData(); formData.append('id', 7195); formData.append('formalisation.selectTitreFichier', 'fiche de formalisation'); formData.append('formalisation.inputTitreFichier', 'fiche de formalisation titre fichier'); formData.append('formalisation.inputFichier', this.form.inputFichier); // 同理添加modop相关字段 axios.post('/create', formData); - 后端调整控制器与DTO:
修改@POST @Path("/create") @Consumes(MediaType.MULTIPART_FORM_DATA) public Response createLien(@BeanParam DocLienDTO docLienDTO) { // 通过FormDataContentDisposition获取文件名等信息 // docLienDTO.getFormalisation().getFileDisposition().getFileName() return Response.status(Status.CREATED).build(); }DocumentDTO的文件相关字段:
注意:需确保Jersey添加了public class DocumentDTO implements Serializable { // ... @FormDataParam("inputFichier") private InputStream inputFichier; @FormDataParam("inputFichier") private FormDataContentDisposition fileDisposition; // ... }jersey-media-multipart依赖,支持表单文件上传。
3. 排查"START_ARRAY"异常
虽然你声明发送的不是数组,但异常提示JSON以数组开头,建议通过浏览器开发者工具的Network面板,查看请求的实际Payload内容,确认是否前端代码误将数据包裹为数组(比如JSON.stringify([data]))。
内容的提问来源于stack exchange,提问作者LBS

