You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jackson与JAX-RS ExceptionMapper返回JSON遇转义引号问题排查

问题分析与解决方案

嘿,这个问题我之前在JAX-RS项目里踩过一模一样的坑!咱们先拆解下问题根源,再给你几个适配你场景的解决办法。

问题根源:两次序列化导致的转义冗余

你看到的"firstProperty" : "{ \"subProperty\" : [ \"val1\" ] }"这种格式,本质是Jackson做了两次序列化:

  1. 你先用ObjectMapper把FirstProperty对象转成了JSON字符串;
  2. 然后把这个字符串塞进了响应POJO的firstProperty字段里;
  3. 最后JAX-RS在构建Response时,又对整个响应POJO做了一次Jackson序列化——这时候原本的JSON字符串就被当成普通文本处理,自动加上了外层引号,内部的引号也被转义了。

简单说就是:你把JSON当成字符串存,Jackson就把它当字符串序列化,自然就会出现多余的引号和转义符。

解决方案:避免两次序列化

针对你不能修改模型类的限制,这里有几个靠谱的方案:

方案1:直接构建JsonNode返回,跳过字符串中转

用Jackson的JsonNode来组装你需要的响应结构,直接返回JsonNode对象,让JAX-RS一次性完成序列化,不会有二次转义的问题:

@Override
public Response toResponse(MyCustomException ex) {
    ObjectMapper mapper = new ObjectMapper();
    ObjectNode errorResponse = mapper.createObjectNode();

    // 获取原始的MainObject实例
    MainObject mainObj = ex.getMainObject();
    FirstProperty firstProp = mainObj.getFirstProperty();

    // 只提取你需要的子属性,比如subProperty
    JsonNode targetSubNode = mapper.valueToTree(firstProp.getSubProperty());
    errorResponse.set("firstProperty", targetSubNode);

    // 还可以加其他自定义字段,比如错误信息
    errorResponse.put("errorMessage", "服务器内部错误");

    return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                   .type(MediaType.APPLICATION_JSON)
                   .entity(errorResponse)
                   .build();
}

这种方式完全绕开了中间字符串的生成,直接操作JSON节点,既保证了只返回需要的属性,又不会出现转义问题。

方案2:动态过滤模型类属性,返回过滤后的POJO

如果你还是想用响应POJO来返回,可以用Jackson的Mixin动态注解+属性过滤器,在不修改原模型类的前提下,过滤掉不需要的属性:

首先定义一个Mixin类,用来给目标模型类绑定过滤器:

@JsonFilter("FirstPropertyFilter")
public abstract class FirstPropertyMixin {}

然后在异常映射器里配置ObjectMapper,动态应用过滤器:

@Override
public Response toResponse(MyCustomException ex) {
    ObjectMapper mapper = new ObjectMapper();

    // 给FirstProperty类动态添加Mixin注解
    mapper.addMixIn(FirstProperty.class, FirstPropertyMixin.class);

    // 创建过滤器:只保留subProperty属性
    SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("subProperty");
    FilterProvider filterProvider = new SimpleFilterProvider().addFilter("FirstPropertyFilter", filter);

    // 构建自定义响应POJO,直接放入原始的FirstProperty对象
    CustomErrorResponse errorResp = new CustomErrorResponse();
    errorResp.setFirstProperty(ex.getMainObject().getFirstProperty());
    errorResp.setErrorMessage("服务器内部错误");

    // 用带过滤器的Writer序列化,避免冗余属性
    String json = mapper.writer(filterProvider).writeValueAsString(errorResp);

    return Response.status(500)
                   .type(MediaType.APPLICATION_JSON)
                   .entity(json)
                   .build();
}

这里关键是用Mixin给原模型类动态加上@JsonFilter,然后通过过滤器只保留你需要的属性,最后直接序列化响应POJO,不会有二次转义。

方案3:直接返回JSON字符串,明确媒体类型

如果你必须先序列化出指定属性的JSON字符串,那一定要确保JAX-RS不会再对这个字符串做序列化——直接把字符串作为实体返回,并指定媒体类型为application/json:

@Override
public Response toResponse(MyCustomException ex) {
    ObjectMapper mapper = new ObjectMapper();
    FirstProperty firstProp = ex.getMainObject().getFirstProperty();

    // 只序列化需要的子属性,比如subProperty
    String targetJson = mapper.writeValueAsString(firstProp.getSubProperty());

    // 组装成最终的响应JSON结构
    String finalJson = String.format("{\"firstProperty\":%s,\"errorMessage\":\"服务器内部错误\"}", targetJson);

    return Response.status(500)
                   .type(MediaType.APPLICATION_JSON)
                   .entity(finalJson)
                   .build();
}

这种方式要注意手动组装JSON时的格式正确性,适合简单的响应结构,复杂结构还是推荐前两种方案。

验证小技巧

你可以在本地测试时,打印一下序列化后的字符串,看看是否已经是正确的JSON格式。如果打印出来的是{"firstProperty":{"subProperty":["val1"]}},那直接返回这个字符串并指定媒体类型就不会有问题;如果打印出来的是带外层引号的"{\"firstProperty\":...}",那肯定是哪里做了二次序列化,得排查代码里的序列化步骤。

内容的提问来源于stack exchange,提问作者Malvon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 07:46:35