JSON Response избежал цитирования, используя Jackson и JAX-RS Exception Mapper - PullRequest
0 голосов
/ 19 мая 2019

У меня есть простое требование, когда, если приложение встречает исключение, моя конечная точка JAX-RS Rest должна возвращать пользовательский ответ JSON с состоянием заголовка HTTP 500.

Данные, необходимые для построения ответа, поступают из объектас несколькими свойствами (см. ниже).Проблема в том, что меня интересуют только одно или два значения из каждого свойства (из нескольких десятков).И я не могу изменить ни одну из этих моделей / классов (у некоторых есть аннотация Джексона для обработки JSON, например, нулевые свойства должны быть отброшены во время сериализации).

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, который я действительно хочу отключить аналогичным образом).

Или явно вернул Object из serializeFirstProperty(), илизаменив все \" на " в serializeFirstProperty() при возврате ser.

Или установите пользовательские StdSerializer 'JsonGenerator.setCharacterEscapes(new CharacterEscapes() { //... } или поиграйте с JAX-RS Response в noпомогло.Мне всегда кажется, что я получаю «строковое» значение с кавычками, например:

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

Если я просто просто сделаю

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

, то каким-то образом получится правильный вывод JSON, однако он включаетмного ненужных свойств, которые мне не нужны в этом случае обработки исключений.

Забавно, когда я вглядываюсь в response (или responseBody map), все выглядит правильно (я несм. значения с двойными кавычками).

Обратите также внимание, что не только я не могу изменять модели, но и некоторые их свойства создаются при создании со значениями по умолчанию, поэтому включение без нуля не работает,и они появятся в окончательном JSON, если я не буду использовать пользовательскую сериализацию.

Кто-нибудь знает, что является причиной этого побега и дополнительных цитат?

1 Ответ

1 голос
/ 19 мая 2019

Я думаю, что неправильно понял вопрос с первой попытки ответить на него.

Проблема в том, что вы сериализуете свойство как строку (используя mapper.writeValueAsString(this), а затем добавляете его в responseBody, который, как вы думаете, является строкой, в объект json map, но это строка для Java-объект map. В вашем случае во время выполнения это строка, сопоставляемая с другой строкой (сериализованный объект json представляется в виде строки Java), а строка Java также является объектом Java.

Вместо этого вы хотите создать объект Java responseBody вместо карты. Он должен действовать как DTO, имеющий все специфические свойства и т. Д., А затем сериализовать его в одно действие, используя маппер. Потому что если вы сначала сериализуете свойство в строку json, то это просто строка с точки зрения Java, и картограф не имеет возможности интерпретировать его как объект json.

...