使用@Produces(MediaType.APPLICATION_JSON)时异常返回字符串报JSON解析错误的解决方法
首先咱们得搞清楚为什么会触发JSON解析错误:你的接口用@Produces(MediaType.APPLICATION_JSON)声明了所有响应都应该是JSON格式,但异常发生时你直接返回了纯字符串(比如"That id is not correct")。JSON解析器会尝试把这个字符串当作JSON解析,可纯字符串(没被双引号正确包裹,或者客户端预期是JSON对象)不符合JSON规范,自然就报错了。
另外你的代码还有个小bug:你先执行了lista.stream().filter(x -> x.getId()==id).findAny().get(),之后才判断id > lista.size()-1。如果id超出范围,findAny().get()会先抛出NoSuchElementException,根本走不到你自己抛异常的步骤,返回的错误信息也不是你预期的内容,这个顺序得先修正。
下面提供几种靠谱的解决方式,你可以根据需求选择:
方案1:统一返回JSON格式的错误响应(推荐)
这种方式最规范,无论成功还是失败都返回JSON,完全符合@Produces的声明,不会有解析问题。
首先创建一个错误响应的实体类:
public class ErrorResponse { private String message; private int statusCode; public ErrorResponse(String message, int statusCode) { this.message = message; this.statusCode = statusCode; } // JAX-RS需要getter来序列化JSON public String getMessage() { return message; } public int getStatusCode() { return statusCode; } }
然后修正接口代码(同时调整判断顺序):
@GET @Path("/paises/{id}") @Produces(MediaType.APPLICATION_JSON) public Response findCountryList(@PathParam("id") int id){ try{ ArrayList<Country> lista = new ArrayList<>(); for(int i=0; i<10 ;i++){ Country c = new Country(); c.setId(i); c.setName("country"+i); c.setCode("countryIsoCode"+i); c.setRegion("region"+i); lista.add(c); } // 先判断id是否合法,避免触发NoSuchElementException if(id < 0 || id > lista.size()-1) { throw new IllegalArgumentException("That id is not correct"); } Country co = lista.stream().filter(x -> x.getId()==id).findAny().get(); return Response.ok(co).build(); } catch(IllegalArgumentException e){ ErrorResponse error = new ErrorResponse(e.getMessage(), 404); return Response.status(404).entity(error).build(); } catch(Exception e){ ErrorResponse error = new ErrorResponse("Unexpected error occurred", 500); return Response.status(500).entity(error).build(); } }
异常时返回的是标准JSON对象,比如:
{"message":"That id is not correct","statusCode":404}
客户端可以正常解析,不会报错。
方案2:动态设置响应的媒体类型
如果不想创建错误实体类,可以在异常时把响应类型改成纯文本,这样客户端就会用文本方式解析,而非JSON。
修正后的代码:
@GET @Path("/paises/{id}") @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) // 声明支持两种响应类型 public Response findCountryList(@PathParam("id") int id){ try{ ArrayList<Country> lista = new ArrayList<>(); for(int i=0; i<10 ;i++){ Country c = new Country(); c.setId(i); c.setName("country"+i); c.setCode("countryIsoCode"+i); c.setRegion("region"+i); lista.add(c); } if(id < 0 || id > lista.size()-1) { throw new IllegalArgumentException("That id is not correct"); } Country co = lista.stream().filter(x -> x.getId()==id).findAny().get(); return Response.ok(co).build(); // 成功时默认返回JSON } catch(IllegalArgumentException e){ return Response.status(404) .type(MediaType.TEXT_PLAIN) // 明确设置为纯文本 .entity(e.getMessage()) .build(); } catch(Exception e){ return Response.status(500) .type(MediaType.TEXT_PLAIN) .entity("Unexpected error occurred") .build(); } }
注意这里要把@Produces改成支持两种媒体类型,否则JAX-RS可能会拒绝返回TEXT_PLAIN类型的响应。
方案3:使用ExceptionMapper全局处理异常
如果项目中有多个接口需要处理类似异常,可以用JAX-RS的ExceptionMapper全局统一处理,不用每个接口都写catch块。
首先创建一个异常映射器:
@Provider public class IllegalArgumentExceptionMapper implements ExceptionMapper<IllegalArgumentException> { @Override public Response toResponse(IllegalArgumentException exception) { ErrorResponse error = new ErrorResponse(exception.getMessage(), 404); return Response.status(404) .type(MediaType.APPLICATION_JSON) .entity(error) .build(); } }
然后接口代码可以大幅简化:
@GET @Path("/paises/{id}") @Produces(MediaType.APPLICATION_JSON) public Response findCountryList(@PathParam("id") int id){ ArrayList<Country> lista = new ArrayList<>(); for(int i=0; i<10 ;i++){ Country c = new Country(); c.setId(i); c.setName("country"+i); c.setCode("countryIsoCode"+i); c.setRegion("region"+i); lista.add(c); } if(id < 0 || id > lista.size()-1) { throw new IllegalArgumentException("That id is not correct"); } Country co = lista.stream().filter(x -> x.getId()==id).findAny().get(); return Response.ok(co).build(); }
当抛出IllegalArgumentException时,JAX-RS会自动调用IllegalArgumentExceptionMapper生成响应,非常优雅。
内容的提问来源于stack exchange,提问作者tzanuttini

