Как получить путь к объекту JSON в Jackson JsonMappingException

#java #json #jackson

#java #json #джексон

Вопрос:

Я использую Jackson для десериализации JSON этой формы:

 {
  "foo" : { "bar" : "baz" } 
}
  

Код Джексона может выглядеть так:

   @JsonCreator
  public class MyProxy {
      @JsonProperty("foo") final FooProxy foo;
  }

  public class FooProxy {
      @JsonProperty("bar") final String bar;
  }
  

Представьте, что потребитель этого API создает недопустимый JSON, подобный этому:

 {
  "foo" : { "bar" : 1 }
}
  

Однако в этом случае я получаю MismatchedInputException сообщение, и ошибка выглядит следующим образом:

Не удается создать экземпляр MyProxy (хотя существует хотя бы один создатель): нет конструктора int / Int-argument / фабричного метода для десериализации из числового значения (1)

Когда я проверяю исключение MismatchedInputException и вызываю ex.getPathReference() , я получаю:

FooProxy[«bar»]->java.lang.Объект [0]

Я хотел бы иметь возможность возвращать путь к нарушенному значению пользователю без какой-либо ссылки на базовые классы Java.

 "foo.bar must be an Object."
  

Как я могу вернуть сообщение об ошибке с путем JSON и удалить любую ссылку на реализацию Java?

Ответ №1:

Что-то вроде этого должно это сделать:

 import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;


public class DeserializationErrorTest {

  @Test
  void testDeserializeWrongInput() throws IOException {
    final ObjectMapper mapper = new ObjectMapper();
    try {
      mapper.readValue("{"foo" : { "bar" : "not-int" }}", MyProxy.class);
    } catch (MismatchedInputException e) {
      throw remapMismatchedInputException(e, RuntimeException.class);
    }
  }

  private <T extends Exception> T remapMismatchedInputException(final MismatchedInputException e, Class<T> exClass) {
    try {
      final String fieldName =
          e.getPath().stream().map(JsonMappingException.Reference::getFieldName).collect(Collectors.joining("."));
      return exClass.getConstructor(String.class).newInstance(fieldName   " must be of type "   e.getTargetType().getSimpleName());
    } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException pE) {
      throw new IllegalArgumentException("Cannot instantiate exception class "   exClass.getSimpleName());
    }
  }

  static class MyProxy {
    @JsonProperty("foo") final FooProxy foo;

    @JsonCreator
    public MyProxy(@JsonProperty("foo") final FooProxy pFoo) {foo = pFoo;}
  }

  static class FooProxy {
    @JsonProperty("bar") final Integer bar;

    @JsonCreator
    public FooProxy(@JsonProperty("bar") final Integer pBar) {bar = pBar;}
  }
}
  

И приведет к:

 java.lang.RuntimeException: foo.bar must be of type Integer
  

Комментарии:

1. e.getPath() возвращает список всех иерархий поля с ошибкой. В случае вложенных объектов ваш метод вернет ту же ошибку для каждого элемента в иерархии