Исключения Джексона - проблемы и решения

Исключения Джексона - проблемы и решения

1. обзор

В этом руководстве мы рассмотримthe most common Jackson Exceptions -JsonMappingException иUnrecognizedPropertyException.

Наконец, мы кратко обсудим Джексона, что нет таких ошибок метода.

Дальнейшее чтение:

Джексон - Custom Serializer

Управляйте выводом JSON с помощью Jackson 2 с помощью пользовательского сериализатора.

Read more

Примеры аннотаций Джексона

Ядром Джексона в основном является набор аннотаций - убедитесь, что вы их хорошо понимаете.

Read more

Начало работы с пользовательской десериализацией в Джексоне

Используйте Джексона, чтобы отобразить пользовательский JSON на любой граф объектов Java с полным контролем над процессом десериализации.

Read more

2. «JsonMappingException: невозможно создать экземпляр»

2.1. Эта проблема

Во-первых, давайте посмотрим на Jsonmappingexception: Can Not Construct Instance Of.

Это исключение возникает, еслиJackson can’t create an instance of the class - это происходит, если классabstract или это простоinterface.

В следующем примере мы пытаемся десериализовать экземпляр из классаZoo, который имеет свойствоanimal с типомabstractAnimal:

public class Zoo {
    public Animal animal;

    public Zoo() { }
}

abstract class Animal {
    public String name;

    public Animal() { }
}

class Cat extends Animal {
    public int lives;

    public Cat() { }
}

Когда мы пытаемся десериализовать JSONString в экземпляр Zoo, он выдает исключение «Jsonmappingexception: Can Not Construct Instance Of», как в следующем примере:

@Test(expected = JsonMappingException.class)
public void givenAbstractClass_whenDeserializing_thenException()
  throws IOException {
    String json = "{"animal":{"name":"lacy"}}";
    ObjectMapper mapper = new ObjectMapper();

    mapper.reader().forType(Zoo.class).readValue(json);
}

full exception:

com.fasterxml.jackson.databind.JsonMappingException:
Can not construct instance of org.example.jackson.exception.Animal,
  problem: abstract types either need to be mapped to concrete types,
  have custom deserializer,
  or be instantiated with additional type information
  at
[Source: {"animal":{"name":"lacy"}}; line: 1, column: 2]
(through reference chain: org.example.jackson.exception.Zoo["animal"])
    at c.f.j.d.JsonMappingException.from(JsonMappingException.java:148)

2.2. Решения

Мы можем решить проблему с помощью простой аннотации -@JsonDeserialize для абстрактного класса:

@JsonDeserialize(as = Cat.class)
abstract class Animal {...}

Если у нас более одного подтипа абстрактного класса, тогда нам следует рассмотреть возможность включения информации о подтипе, как показано в этом посте:Inheritance with Jackson.

3. JsonMappingException: нет подходящего конструктора

3.1. Эта проблема

А теперь давайте посмотрим на распространенное исключение Jsonmappingexception: Нет подходящего конструктора found for type.

Это исключение выдается, еслиJackson can’t access the constructor.

В следующем примере - классUser не имеет конструктора по умолчанию:

public class User {
    public int id;
    public String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

Когда мы пытаемся десериализовать строку JSON для пользователя, возникает исключение «Jsonmappingexception: не найдено подходящего конструктора» - как в следующем примере:

@Test(expected = JsonMappingException.class)
public void givenNoDefaultConstructor_whenDeserializing_thenException()
  throws IOException {
    String json = "{"id":1,"name":"John"}";
    ObjectMapper mapper = new ObjectMapper();

    mapper.reader().forType(User.class).readValue(json);
}

full exception:

com.fasterxml.jackson.databind.JsonMappingException:
No suitable constructor found for type
[simple type, class org.example.jackson.exception.User]:
 can not instantiate from JSON object (need to add/enable type information?)
 at [Source: {"id":1,"name":"John"}; line: 1, column: 2]
        at c.f.j.d.JsonMappingException.from(JsonMappingException.java:148)

3.2. Решение

Чтобы решить эту проблему - просто добавьте конструктор по умолчанию, как в следующем примере:

public class User {
    public int id;
    public String name;

    public User() {
        super();
    }

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

Теперь, когда мы десериализовать - процесс будет работать нормально:

@Test
public void givenDefaultConstructor_whenDeserializing_thenCorrect()
  throws IOException {

    String json = "{"id":1,"name":"John"}";
    ObjectMapper mapper = new ObjectMapper();

    User user = mapper.reader()
      .forType(User.class).readValue(json);
    assertEquals("John", user.name);
}

4. JsonMappingException: имя корня не соответствует ожидаемому

4.1. Эта проблема

Далее - давайте взглянем на Jsonmappingexception: имя корневого каталога не соответствует ожидаемому.

Это исключение выдается, еслиthe JSON doesn’t match exactly what Jackson is looking for; например, основной JSON можно обернуть, как в следующем примере:

@Test(expected = JsonMappingException.class)
public void givenWrappedJsonString_whenDeserializing_thenException()
  throws IOException {
    String json = "{"user":{"id":1,"name":"John"}}";

    ObjectMapper mapper = new ObjectMapper();
    mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);

    mapper.reader().forType(User.class).readValue(json);
}

full exception:

com.fasterxml.jackson.databind.JsonMappingException:
Root name 'user' does not match expected ('User') for type
 [simple type, class org.example.jackson.dtos.User]
 at [Source: {"user":{"id":1,"name":"John"}}; line: 1, column: 2]
   at c.f.j.d.JsonMappingException.from(JsonMappingException.java:148)

4.2. Решение

Мы можем решить эту проблему с помощью аннотации@JsonRootName - как в следующем примере:

@JsonRootName(value = "user")
public class UserWithRoot {
    public int id;
    public String name;
}

Когда мы пытаемся десериализовать упакованный JSON - он работает правильно:

@Test
public void
  givenWrappedJsonStringAndConfigureClass_whenDeserializing_thenCorrect()
  throws IOException {

    String json = "{"user":{"id":1,"name":"John"}}";

    ObjectMapper mapper = new ObjectMapper();
    mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);

    UserWithRoot user = mapper.reader()
      .forType(UserWithRoot.class)
      .readValue(json);
    assertEquals("John", user.name);
}

5. JsonMappingException: для класса не найден сериализатор

5.1. Эта проблема

Теперь давайте посмотрим на Jsonmappingexception: для класса не найден сериализатор.

Это исключение выдается при попыткеserialize an instance while its properties and their getters are private.

В следующем примере мы пытаемся сериализовать «UserWithPrivateFields»:

public class UserWithPrivateFields {
    int id;
    String name;
}

Когда мы пытаемся сериализовать экземпляр «UserWithPrivateFields», выдается исключение «Jsonmappingexception: сериализатор для класса не найден», как в следующем примере:

@Test(expected = JsonMappingException.class)
public void givenClassWithPrivateFields_whenSerializing_thenException()
  throws IOException {
    UserWithPrivateFields user = new UserWithPrivateFields(1, "John");

    ObjectMapper mapper = new ObjectMapper();
    mapper.writer().writeValueAsString(user);
}

Полное исключение:

com.fasterxml.jackson.databind.JsonMappingException:
No serializer found for class org.example.jackson.exception.UserWithPrivateFields
 and no properties discovered to create BeanSerializer
(to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) )
  at c.f.j.d.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:59)

5.2. Решение

Мы можем решить эту проблему, настроив видимостьObjectMapper - как в следующем примере:

@Test
public void givenClassWithPrivateFields_whenConfigureSerializing_thenCorrect()
  throws IOException {

    UserWithPrivateFields user = new UserWithPrivateFields(1, "John");

    ObjectMapper mapper = new ObjectMapper();
    mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);

    String result = mapper.writer().writeValueAsString(user);
    assertThat(result, containsString("John"));
}

Или используйте аннотацию@JsonAutoDetect - как в следующем примере:

@JsonAutoDetect(fieldVisibility = Visibility.ANY)
public class UserWithPrivateFields { ... }

Конечно, если у нас есть возможность изменить источник класса, мы также можем добавить в геттеры для использования Джексоном.

6. JsonMappingException: невозможно десериализовать экземпляр

6.1. Эта проблема

Далее - давайте посмотрим на Jsonmappingexception: Can Not Deserialize Instance Of.

Это исключение выдается, еслиthe wrong type is used во время десериализации.

В следующем примере мы пытаемся десериализоватьList изUser:

@Test(expected = JsonMappingException.class)
public void givenJsonOfArray_whenDeserializing_thenException()
  throws JsonProcessingException, IOException {

    String json
      = "[{"id":1,"name":"John"},{"id":2,"name":"Adam"}]";
    ObjectMapper mapper = new ObjectMapper();
    mapper.reader().forType(User.class).readValue(json);
}

full exception:

com.fasterxml.jackson.databind.JsonMappingException:
Can not deserialize instance of
  org.example.jackson.dtos.User out of START_ARRAY token
  at [Source: [{"id":1,"name":"John"},{"id":2,"name":"Adam"}]; line: 1, column: 1]
  at c.f.j.d.JsonMappingException.from(JsonMappingException.java:148)

6.2. Решение

Мы можем решить эту проблему, изменив тип сUser наList<User> - как в следующем примере:

@Test
public void givenJsonOfArray_whenDeserializing_thenCorrect()
  throws JsonProcessingException, IOException {

    String json
      = "[{"id":1,"name":"John"},{"id":2,"name":"Adam"}]";

    ObjectMapper mapper = new ObjectMapper();
    List users = mapper.reader()
      .forType(new TypeReference>() {})
      .readValue(json);

    assertEquals(2, users.size());
}

7. UnrecognizedPropertyExceptionс

7.1. Эта проблема

А теперь давайте посмотрим наUnrecognizedPropertyException.

Это исключение выдается, если при десериализации имеется строкаunknown property in the JSON.

В следующем примере мы пытаемся десериализовать строку JSON с дополнительным свойством «checked»:

@Test(expected = UnrecognizedPropertyException.class)
public void givenJsonStringWithExtra_whenDeserializing_thenException()
  throws IOException {

    String json = "{"id":1,"name":"John", "checked":true}";

    ObjectMapper mapper = new ObjectMapper();
    mapper.reader().forType(User.class).readValue(json);
}

full exception:

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "checked" (class org.example.jackson.dtos.User),
 not marked as ignorable (2 known properties: "id", "name"])
 at [Source: {"id":1,"name":"John", "checked":true}; line: 1, column: 38]
 (through reference chain: org.example.jackson.dtos.User["checked"])
  at c.f.j.d.exc.UnrecognizedPropertyException.from(
    UnrecognizedPropertyException.java:51)

7.2. Решение

Мы можем решить эту проблему, настроивObjectMapper - как в следующем примере:

@Test
public void givenJsonStringWithExtra_whenConfigureDeserializing_thenCorrect()
  throws IOException {

    String json = "{"id":1,"name":"John", "checked":true}";

    ObjectMapper mapper = new ObjectMapper();
    mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

    User user = mapper.reader().forType(User.class).readValue(json);
    assertEquals("John", user.name);
}

Или мы можем использовать аннотацию@JsonIgnoreProperties:

@JsonIgnoreProperties(ignoreUnknown = true)
public class User {...}

8. JsonParseException: Неожиданный символ (”'(код 39))

8.1. Эта проблема

Далее - давайте обсудимJsonParseException: Unexpected character (”' (code 39)).

Это исключение выдается, еслиthe JSON String to be deserialized contains single quotes вместо двойных кавычек.

В следующем примере - мы пытаемся десериализовать строку JSON, содержащую одинарные кавычки:

@Test(expected = JsonParseException.class)
public void givenStringWithSingleQuotes_whenDeserializing_thenException()
  throws JsonProcessingException, IOException {

    String json = "{'id':1,'name':'John'}";
    ObjectMapper mapper = new ObjectMapper();

    mapper.reader()
      .forType(User.class).readValue(json);
}

full exception:

com.fasterxml.jackson.core.JsonParseException:
Unexpected character (''' (code 39)):
  was expecting double-quote to start field name
  at [Source: {'id':1,'name':'John'}; line: 1, column: 3]
  at c.f.j.core.JsonParser._constructError(JsonParser.java:1419)

8.2. Решение

We can solve this by configuring the ObjectMapper to allow single quotes:

@Test
public void
  givenStringWithSingleQuotes_whenConfigureDeserializing_thenCorrect()
  throws JsonProcessingException, IOException {

    String json = "{'id':1,'name':'John'}";

    JsonFactory factory = new JsonFactory();
    factory.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
    ObjectMapper mapper = new ObjectMapper(factory);

    User user = mapper.reader().forType(User.class)
      .readValue(json);

    assertEquals("John", user.name);
}

9. Jackson NoSuchMethodError

Finally – let’s quickly discuss the Jackson “No such method” errors.

When java.lang.NoSuchMethodError Exception is thrown, it is usually because you have multiple (and incompatible) versions of Jackson jars on your classpath.

full exception:

java.lang.NoSuchMethodError:
com.fasterxml.jackson.core.JsonParser.getValueAsString()Ljava/lang/String;
 at c.f.j.d.deser.std.StringDeserializer.deserialize(StringDeserializer.java:24)

10. Заключение

In this article, we did a deep dive in the most common Jackson problems – exceptions and errors, looking at the potential causes and at the solutions for each one.

Реализация всех этих примеров и фрагментов кодаcan be found on Github - это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.