Exceções de Jackson - Problemas e Soluções

Exceções de Jackson - Problemas e Soluções

1. Visão geral

Neste tutorial, examinaremosthe most common Jackson Exceptions - osJsonMappingExceptioneUnrecognizedPropertyException.

Finalmente - vamos discutir brevemente Jackson sem erros de método.

Leitura adicional:

Jackson - Serializador personalizado

Controle sua saída JSON com o Jackson 2 usando um serializador personalizado.

Read more

Exemplos de anotações de Jackson

O núcleo de Jackson é basicamente um conjunto de anotações - certifique-se de entendê-las bem.

Read more

Introdução à desserialização personalizada em Jackson

Use Jackson para mapear JSON personalizado para qualquer gráfico de entidade java com controle total sobre o processo de desserialização.

Read more

2. “JsonMappingException: Não é possível construir a instância de”

2.1. O problema

Primeiro - vamos dar uma olhada em Jsonmappingexception: Can Not Construct Instance Of.

Essa exceção é lançada seJackson can’t create an instance of the class - isso acontece se a classe forabstract ou for apenas uminterface.

No exemplo a seguir - tentamos desserializar uma instância da classeZoo que tem uma propriedadeanimal comabstract tipoAnimal:

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

Quando tentamos desserializar um JSONString para a instância Zoo, ele lança a "exceção Jsonmapping: Can Not Construct Instance Of" como no exemplo a seguir:

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

Ofull 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. Soluções

Podemos resolver o problema com uma anotação simples -@JsonDeserialize na classe abstrata:

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

Se tivermos mais de um subtipo da classe abstrata, devemos considerar a inclusão das informações do subtipo conforme mostrado neste post:Inheritance with Jackson.

3. JsonMappingException: Nenhum construtor adequado

3.1. O problema

Agora - vamos dar uma olhada na exceção Jsonmapping comum: Nenhum construtor adequado found for type.

Esta exceção é lançada seJackson can’t access the constructor.

No exemplo a seguir - classeUser não tem um construtor padrão:

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

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

Quando tentamos desserializar uma string JSON para o usuário, uma exceção "Jsonmappingexception: Nenhum Construtor Adequado encontrado" é lançada - como no exemplo a seguir:

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

Ofull 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. A solução

Para resolver esse problema - basta adicionar um construtor padrão, como no exemplo a seguir:

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

    public User() {
        super();
    }

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

Agora, quando desserializarmos - o processo funcionará perfeitamente:

@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: o nome da raiz não corresponde ao esperado

4.1. O problema

A seguir - vamos dar uma olhada na exceção Jsonmapping: o nome da raiz não corresponde ao esperado.

Esta exceção é lançada sethe JSON doesn’t match exactly what Jackson is looking for; por exemplo, o JSON principal pode ser agrupado como no exemplo a seguir:

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

Ofull 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. A solução

Podemos resolver este problema usando a anotação@JsonRootName - como no exemplo a seguir:

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

Quando tentamos desserializar o JSON empacotado - ele funciona corretamente:

@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: Nenhum serializador encontrado para a classe

5.1. O problema

Agora - vamos dar uma olhada em Jsonmappingexception: Nenhum serializador encontrado para a classe.

Esta exceção é lançada se você tentarserialize an instance while its properties and their getters are private.

No exemplo a seguir - tentamos serializar um “UserWithPrivateFields“:

public class UserWithPrivateFields {
    int id;
    String name;
}

Quando tentamos serializar uma instância de “UserWithPrivateFields” - uma exceção “Jsonmappingexception: Nenhum serializador encontrado para classe” é lançada como no exemplo a seguir:

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

A exceção completa é:

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. A solução

Podemos resolver este problema configurando a visibilidadeObjectMapper - como no exemplo a seguir:

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

Ou usando a anotação@JsonAutoDetect - como no exemplo a seguir:

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

Obviamente, se tivermos a opção de modificar a fonte da classe, também podemos adicionar getters para Jackson usar.

6. JsonMappingException: Não é possível desserializar a instância de

6.1. O problema

A seguir - vamos dar uma olhada na exceção Jsonmapping: Não é possível desserializar a instância de.

Esta exceção é lançada sethe wrong type is used durante a desserialização.

No exemplo a seguir - estamos tentando desserializar umList deUser:

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

Ofull 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. A solução

Podemos resolver esse problema alterando o tipo deUser paraList<User> - como no exemplo a seguir:

@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. O problema

Agora - vamos ver oUnrecognizedPropertyException.

Essa exceção é lançada se houver uma stringunknown property in the JSON durante a desserialização.

No exemplo a seguir - tentamos desserializar uma String JSON com a propriedade extra “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);
}

Ofull 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. A solução

Podemos resolver esse problema configurando oObjectMapper - como no exemplo a seguir:

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

Ou podemos usar a anotação@JsonIgnoreProperties:

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

8. JsonParseException: caractere inesperado (”'(código 39))

8.1. O problema

A seguir - vamos discutirJsonParseException: Unexpected character (”' (code 39)).

Esta exceção é lançada sethe JSON String to be deserialized contains single quotes em vez de aspas duplas.

No exemplo a seguir - tentamos desserializar uma string JSON contendo aspas simples:

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

Ofull 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. A solução

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.

Ofull 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. Conclusão

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.

A implementação de todos esses exemplos e trechos de códigocan be found on Github - este é um projeto baseado em Maven, portanto, deve ser fácil de importar e executar como está.