【问题标题】:JSON Response Has Escaped Quotations Using Jackson and JAX-RS Exception MapperJSON 响应已使用 Jackson 和 JAX-RS 异常映射器转义引号
【发布时间】:2019-10-05 19:56:39
【问题描述】:

我有一个简单的要求,如果应用程序遇到异常,我的 JAX-RS Rest 端点应该返回一个带有 500 HTTP 标头状态的自定义 JSON 响应。

构建响应所需的数据来自具有多个属性的对象(见下文)。问题是,我只对每个属性的一个或两个值感兴趣(几十个)。而且我无法修改这些模型/类中的任何一个(有些具有用于 JSON 处理的 Jackson 注释,例如,在序列化期间应丢弃空属性)。

public class MainObject {
  private FirstProperty firstProperty;
  private SecondProperty secondProperty;
  private ThirdProperty thirdProperty;
  // other codes
  public String toString() {
    ObjectMapper mapper = new ObjectMapper();
    try { return mapper.writeValueAsString(this); }
    catch (Exception e) {  return null; }
  }
}

public class FirstProperty {
  private boolean bol = true;
  private double dob = 5.0;
  private List<String> subProperty;
  // other properties
  public String toString() {
    ObjectMapper mapper = new ObjectMapper();
    try { return mapper.writeValueAsString(this); }
    catch (Exception e) {  return null; }
  }
}

@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class SecondProperty {
  private String str;
  private List<String> subProperty;
  // other properties
  public String toString() {
    ObjectMapper mapper = new ObjectMapper();
    try { return mapper.writeValueAsString(this); }
    catch (Exception e) {  return null; }
  }
}

@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class ThirdProperty {
  private int intProp = true;
  private List<String> subProperty;
  // other properties
  public String toString() {
    ObjectMapper mapper = new ObjectMapper();
    try { return mapper.writeValueAsString(this); }
    catch (Exception e) {  return null; }
  }
}

我应该看到返回的预期 JSON 在客户端(例如,浏览器 -- 在 Edge 中测试):

{
  "firstProperty" : { "subProperty" : [ "val1" ] },
  "secondProperty" : { "str" : "val2", "subproperty" : [ "val3", "val6" ] },
  "thirdProperty" : { "subProperty" : [ "val4" ] }
}

相反,我所有的字段名称和它们的值都被转义了它们的引号,并且整个值周围都有额外的双引号,例如:

{
  "firstProperty" : "{ \"subProperty\" : [ \"val1\" ] }",
  "secondProperty" : "{ \"str\" : \"val2\", \"subproperty\" : [ \"val3\", \"val6\" ] }",
  "thirdProperty" : "{ \"subProperty\" : [ \"val4\" ] }"
}

请注意花括号前后的额外"。我的环境是:

Java 1.8.45
FasterXML Jackson 2.9.8
Spring Boot 2.0.1
RestEasy (JBoss) JAX-RS
JBoss 6.4

我消除了代码中的大部分“噪音”,看看在什么时候会发生这种情况。这是控制器:

@Path("/")
public class MainController {

  @GET
  @Produces(MediaType.APPLICATION_JSON)
  @Path("/rest/path")
  public MainObject getMainObject throws MyCustomException {
    // A service call that throws MyCustomException
  }
}

还有 JAX-RS ExceptionMapper 我将响应发回:

@Provider
public class MyCustomExceptionMapper extends ExceptionMapper<MyCustomException> {

  @Override
  public Response toResponse(MyCustomException ex) {
    HashMap<String, Object> responseBody = new HashMap<>();

    String strEx = ex.getStrEx(); // Comes from SecondProperty.str stored in MyCustomException, not that it matters

    // Instantiate an empty object that contains 
    MainObject obj = new MainObject();
    obj.getFirstProperty().setSubProperty(ex.getStrs());
    obj.getSecondProperty().setStr(strEx);
    obj.getSecondProperty().setSubProperty(ex.getStrs());
    obj.getThirdProperty().setSubProperty(ex.getStrs());

    responseBody.put("firstProperty", serializeFirstProperty(obj.getFirstProperty()));
    responseBody.put("secondProperty", serializeSecondProperty(obj.getSecondProperty()));
    responseBody.put("thirdProperty", serializeThirdProperty(obj.getThirdProperty()));

    Response response = Response.status(/* 500 status */).entity(responseBody).build();

    return response;
  }

}

由于我只需要从我的每个类型中发回一个很小的整体属性子集,我创建了一个自定义StdSerializer,它只会填充所需的属性。为简洁起见,我只做serializeFirstProperty(),但它们或多或少都相同:

private StdSerializer<FirstProperty> getFPSerializer(FirstProperty firstProperty) {
  return new StdSerializer<FirstProperty>(FirstProperty.class) {
    @Override
    public void serialize(FirstProperty value, JsonGenerator gen, SerializerProvider provider) throws IOException {

      gen.writeStartObject();
      if (/* there are items in FirstProperty.subProperty */) {
        gen.writeArrayFieldStart("subProperty");
        for (String str : value.getSubProperty()) {
          gen.writeString(str);
        }
        gen.writeEndArray();
      }
      gen.writeEndObject();
    }
}

private <T> ObjectMapper getCustomOM(StdSerializer<?> serializer) {
  ObjectMapper mapper = new ObjectMapper();

  SimpleModule sm = new SimpleModule();
  sm.addSerializer(serializer);
  mapper.registerModule(module);

  return mapper;
}

然后使用这些辅助方法,例如:

private String serializeFirstProperty(FirstProperty firstProperty) {
  ObjectMapper mapper = getCustomOM(getFPSerializer(firstProperty));

  String ser = null;
  try { ser = mapper.writeValueAsString(firstProperty); }
  catch (JsonProcessingException e) { return null; }
  return ser;
}

我用ObjectMapper 尝试了无数种配置,例如disable(JsonParser.Feature.ALLOW_BACKLASH_ESCAPING_ANY_CHARACTER)(找不到任何与 JsonGenerator 相关的标志,我真的想以类似的方式禁用它)。

或者从serializeFirstProperty()显式返回Object,或者在返回ser时将serializeFirstProperty()中的所有\"替换为"

或设置自定义 StdSerializerJsonGenerator.setCharacterEscapes(new CharacterEscapes() { //... } 或玩弄 JAX-RS Response 无济于事。我似乎总是得到一个带引号的“字符串”值,例如:

"firstProperty" : "{ \"subProperty\" : [ \"val1\" ] }"

如果我只是这样做

responseBody.put("firstProperty", mapper.writeValueAsString(obj.getFirstProperty()));

这会以某种方式产生正确的 JSON 输出,但是,它包含许多在此异常处理案例中我不想要的不必要的属性。

有趣的是,当我凝视response(或responseBody 地图)时,一切看起来都是正确的(我没有看到带有双引号的值)。

还请注意,我不仅不能修改模型,而且它们的一些属性在创建过程中使用默认值进行实例化,因此非空包含不起作用,如果我这样做,它们将出现在最终的 JSON 中不要使用自定义序列化。

有谁知道是什么导致了这个转义和额外的引号?

【问题讨论】:

    标签: java json jax-rs resteasy jackson2


    【解决方案1】:

    我想我在第一次尝试回答时误解了这个问题。

    问题是您将属性序列化为字符串(使用 mapper.writeValueAsString(this) 然后将其添加到您认为是字符串到 json object 映射的 responseBody 但它是一个字符串Java 对象 映射。在您的情况下,在运行时它是一个字符串映射到另一个字符串(序列化的 json 对象表示为 Java 字符串),Java 字符串也是一个 Java 对象。

    您想要做的是构造一个Java 对象responseBody 而不是映射。它应该充当具有所有特定属性等的 DTO,然后使用映射器在一个操作中将其序列化。因为如果您首先将属性序列化为 json 字符串,那么从 Java 的角度来看,它只是一个字符串,并且映射器没有机会将其解释为 json 对象。

    【讨论】:

    • 你的意思是mapper.writeValue(firstProperty)com.fasterxml.jackson.databind.ObjectMapperwriteValue(Object) 吗?我从未在 API 或文档中遇到过。
    • 或者您指的是模型的toString() 方法(正如我所提到的,我无法修改这些类,因为它们无论如何都会将对象返回给客户端,并且此更改仅适用于异常的情况处理)?
    • 如果我在每个模型的toString() 处设置一个断点,它们就永远不会被击中。事实上,我没有看到我调用任何模型的toString() 的地方(如果你看到了,请指出)。是的,我的印象是它应该是“Java 对象的字符串”,因此是Map&lt;String, Object&gt;。我明白你在这里问什么,也就是说,基本上创建另一个模型并像现有模型一样对其进行序列化。我试试看。
    • 附注:如果我用 Response 更改 JAX-RS 的媒体类型,例如Response.type(MediaType.TEXT_PLAIN),然后它将正确的格式返回给浏览器/客户端。但是,由于它是 text/plain 而不是 application/json,因此 React 组件会出错。
    • 知道了。谢谢。我想,我本可以使用相同的MainObject 并围绕它开发一个自定义序列化程序,仅填充特定字段并忽略其他所有内容。但是,我需要在响应中包含一个额外的值(我的问题中没有包含它),它不是MainObject 的一部分。我猜除了反射性地将它附加到MainObject 然后运行自定义序列化程序之外,我不知道如何仅通过操作“MainObject”来完成。我认为正如您所提到的,最好只创建另一个 DTO 模型来处理这些异常情况。
    猜你喜欢
    • 2011-03-18
    • 1970-01-01
    • 2013-04-24
    • 1970-01-01
    • 1970-01-01
    • 2015-10-07
    • 1970-01-01
    • 2016-11-01
    • 1970-01-01
    相关资源
    最近更新 更多