Contents

Jackson 异常

1. 概述

在本教程中,我们将介绍最常见的 Jackson 异常- JsonMappingExceptionUnrecognizedPropertyException。 最后,我们将简要讨论“NoSuchMethodError”的错误。

2. JsonMappingException : Can Not Construct Instance of

2.1. 问题

首先我们来看看JsonMappingException: Can Not Construct Instance Of。 **如果Jackson 无法创建该类的实例,**则会引发此异常,如果该类是抽象类或它只是一个接口,则会发生这种情况。 在这里,我们将尝试反序列化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字符串反序列化为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 com.blogdemo.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: com.blogdemo.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: No applicable Constructor found for type

**如果Jackson 无法访问构造函数,**则会引发此异常。 在以下示例中,类User没有默认构造函数:

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

当我们尝试将 JSON 字符串反序列化为 User 时,会抛出 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 com.blogdemo.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: Root Name Does Not Match Expected。 **如果JSON 与 Jackson 查找的内容不完全匹配,**则会引发此异常。 例如,可以包装主 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 com.blogdemo.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 : No Serializer Found for Class

5.1. 问题

现在让我们看一下 JsonMappingException: No Serializer Found for Class。

如果我们尝试序列化一个实例,而它的属性和它们的 getter 是私有的,则会引发此异常。 我们将尝试序列化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 com.blogdemo.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 { ... }

当然,如果我们确实可以选择修改类的来源,我们也可以添加 getter 供 Jackson 使用。

6. JsonMappingException : Can Not Deserialize Instance of

6.1. 问题

接下来我们看一下JsonMappingException: Can Not Deserialize Instance Of。

**如果在反序列化时使用了错误的类型,**则会引发此异常。 在这个例子中,我们试图反序列化一个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);
}

这是完整的例外

com.fasterxml.jackson.databind.JsonMappingException:
Can not deserialize instance of 
  com.blogdemo.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. UnrecognizedPropertyException

7.1. 问题

现在让我们看看UnrecognizedPropertyException。 如果反序列化时 JSON 字符串中存在未知属性,则会引发此异常。 我们将尝试反序列化带有额外属性“ checked ”的 JSON 字符串:

@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 com.blogdemo.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: com.blogdemo.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 : Unexpected Character (”’ (code 39))

8.1. 问题

接下来,让我们讨论JsonParseException: Unexpected character (”’ (code 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

最后,让我们快速讨论一下NoSuchMethodError的错误。

当抛出java.lang.NoSuchMethodError异常时,通常是因为我们的类路径上有多个(且不兼容的)Jackson jar 版本。 这是完整的例外

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