Перейти к основному содержимому

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

· 8 мин. чтения

Задача: Сумма двух чисел

Напишите функцию twoSum. Которая получает массив целых чисел nums и целую сумму target, а возвращает индексы двух чисел, сумма которых равна target. Любой набор входных данных имеет ровно одно решение, и вы не можете использовать один и тот же элемент дважды. Ответ можно возвращать в любом порядке...

ANDROMEDA

1. Обзор

В этом руководстве мы рассмотрим самые распространенные исключения ДжексонаJsonMappingException и UnrecognizedPropertyException .

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

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

2.1. Проблема

Во-первых, давайте взглянем на JsonMappingException: Can Not Construct Instance Of.

Это исключение возникает, если Джексон не может создать экземпляр класса , что происходит, если класс является абстрактным или представляет собой просто интерфейс .

Здесь мы попробуем десериализовать экземпляр из класса Zoo , у которого есть свойство animal с абстрактным типом Animal :

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() { }
}

Когда мы пытаемся десериализовать JSON String в экземпляр 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);
}

Это полное исключение :

com.fasterxml.jackson.databind.JsonMappingException: 
Can not construct instance of org.foreach.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.foreach.jackson.exception.Zoo["animal"])
at c.f.j.d.JsonMappingException.from(JsonMappingException.java:148)

2.2. Решения

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

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

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

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

3.1. Проблема

Теперь давайте посмотрим на обычное исключение JsonMappingException: для типа не найден подходящий конструктор .

Это исключение выдается, если Джексон не может получить доступ к конструктору.

В следующем примере класс 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);
}

И это полное исключение :

com.fasterxml.jackson.databind.JsonMappingException: 
No suitable constructor found for type
[simple type, class org.foreach.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: корневое имя не соответствует ожидаемому.

Это исключение возникает, если JSON не соответствует тому, что ищет Джексон.

Например, основной 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);
}

Это полное исключение :

com.fasterxml.jackson.databind.JsonMappingException:
Root name 'user' does not match expected ('User') for type
[simple type, class org.foreach.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: для класса не найден сериализатор.

Это исключение возникает, если мы пытаемся сериализовать экземпляр, пока его свойства и их геттеры являются закрытыми.

Мы попробуем сериализовать UserWithPrivateFields :

public class UserWithPrivateFields {
int id;
String name;
}

Когда мы пытаемся сериализовать экземпляр UserWithPrivateFields , выдается JsonMappingException: No Serializer Found for Class:

@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.foreach.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.

Это исключение возникает, если при десериализации используется неправильный тип .

В этом примере мы пытаемся десериализовать список пользователей : ``

@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);
}

И вот полное исключение :

com.fasterxml.jackson.databind.JsonMappingException:
Can not deserialize instance of
org.foreach.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<User> users = mapper.reader()
.forType(new TypeReference<List<User>>() {})
.readValue(json);

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

7. Исключение нераспознанного свойства

7.1. Проблема

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

Это исключение возникает, если во время десериализации в строке 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);
}

Это полное исключение :

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "checked" (class org.foreach.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.foreach.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: Неожиданный символ (”' (код 39)) .

Это исключение возникает, если десериализуемая строка JSON содержит одинарные кавычки вместо двойных.

Мы попробуем десериализовать строку 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);
}

Вот полное исключение :

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. Решение

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

@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. Джексон NoSuchMethodError

Наконец, давайте быстро обсудим ошибки Джексона «Нет такого метода».

Когда возникает исключение java.lang.NoSuchMethodError Exception, обычно это происходит потому, что у нас есть несколько (и несовместимых) версий jar-файлов Jackson в нашем пути к классам.

Это полное исключение :

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. Заключение

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

Реализацию всех этих примеров и фрагментов кода можно найти на GitHub . Это проект на основе Maven, поэтому его легко импортировать и запускать как есть.