Jackson异常问题和解决方案总结

转自:http://www.baeldung.com/jackson-exception,这是我在baeldung看到的一篇关于Jackson的异常和解决的总结,觉得写的不错,自己也遇到过里面的一些问题,就翻译过来啦,收藏一下。

1. 前言

在本教程中, 我们会回顾最常见的Jackson异常 – theJsonMappingException andUnrecognizedPropertyException.

最后,我们将会简要讨论Jackson 中的no such method错误。

2. “JsonMappingException: Can not construct instance of”

2.1. The Problem

首先我们来看JsonMappingException: Can not construct instance of.

此异常是由Jackson不能完成一个对象的构造导致的 – 如果序反序列化的类是抽象类或者接口,就会导致这个异常。这里给出一个例子 – 我们想要反序列化一个Zoo对象,Zoo中有一个属性是抽象类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 org.baeldung.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.baeldung.jackson.exception.Zoo["animal"])
    at c.f.j.d.JsonMappingException.from(JsonMappingException.java:148)

2.2. The Solution

可以通过在抽象类上添加@JsonDeserialize注解解决:

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

3. JsonMappingException: No suitable constructor

3.1. The Problem

现在我们来看另外一个常见的异常JsonMappingException: No suitable 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: No suitable constructor found” ,代码如下:

@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.baeldung.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. The Solution

解决这个问题只需要在类中添加一个默认的构造函数即可:

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: Root name does not match expected

4.1. The Problem

接下来我们来看JsonMappingException: Root name does not match expected.

这个异常是由于JSON字符串和Jackson找到的类不匹配导致的,例如下面的JSON字符串反序列化为User时,会抛此异常:

@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.baeldung.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. The Solution

我们可以通过@JsonRootName注解来解决这个问题,如下:

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

这样我们再运行反序列化时就能正常运行了:

@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. The Problem

现在我们来看异常JsonMappingException: No serializer found for class.

此异常是由于我们在序列化一个对象时,此对象的属性和getter方法都是private修饰的,也就是私有的,Jackson不能访问到。

例如– 我们想序列化一个 “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.baeldung.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. The Solution

我们可以通过配置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方法解决,这个方法应该是首选的方法。

6. JsonMappingException: Can not deserialize instance of

6.1. The Problem

接下来看异常JsonMappingException: Can not deserialize instance of.

这个异常是由于在反序列化时类型使用不当导致的,例如我们想反序列化一个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);
}
完整异常:
com.fasterxml.jackson.databind.JsonMappingException:
Can not deserialize instance of 
  org.baeldung.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. The Solution

可以通过更改UserList<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. The Problem

接下来我们来看UnrecognizedPropertyException.

此异常是由于我们在反序列化时,JSON字符串中出现了未知的属性导致的,比如我们想反序列化一个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.baeldung.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.baeldung.jackson.dtos.User["checked"])
  at c.f.j.d.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:51)

7.2. The Solution

我们可以通过下面的方法配置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. The Problem

接下来我们讨论JsonParseException: Unexpected character (”’ (code 39)).

当我们反序列化的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. The Solution

我们也可以通过配置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. Jackson NoSuchMethodError

接下来我们快速的讨论一下 “No such method” errors.

当抛了java.lang.NoSuchMethodError 异常时, 通常是因为你又多个Jackson的jar包导致的。不只是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)

10. Conclusion

在这篇文章中,我们深一层解析了常见的Jackson中的异常和错误,并且对每一个异常和错误分析了原因和解决方案。以上所有的代码都可以在Github上找到,这是一个基于Maven的工程,很容易就可以导入并且运行拉。


已标记关键词 清除标记
<div><p>I have a use case (described also in this Stackoverflow question: http://stackoverflow.com/questions/30264115/how-to-use-jackson-deserializer-converter-correctly) where I would like to implement custom parsing for string values into types based on a legacy parser. I implemented a converter extending <code>StdConverter</code> that takes a string and runs my legacy parser to parse it in my desired type. I then tried to use <code>` with</code>converter` argument. </p> <p>This works when my field is a concrete class. But if my field is declared to be of an abstract type, even if the converter correctly returns a concrete instance, I get an exception.</p> <p>More specifically, in the test case below, I would expect both <code>testNonAbstractTypeDeserialization</code> and <code>testAbstractTypeDeserialization</code> to pass, because they use very similar converters. But in practice (Jackson 2.5.0) <code>testNonAbstractTypeDeserialization</code> passes, and <code>testAbstractTypeDeserialization</code> fails with the following exception:</p> <pre><code> com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of JacksonTest$AbstractCustomType, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information at [Source: {"customField": "customString"}; line: 1, column: 2] </code></pre> <p>The JUnit test case is below</p> <pre><code> import java.io.IOException; import org.junit.Test; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.type.TypeFactory; import com.fasterxml.jackson.databind.util.Converter; import com.fasterxml.jackson.databind.util.StdConverter; import static org.junit.Assert.*; public class JacksonTest { public static abstract class AbstractCustomType { private final String value; public AbstractCustomType(String v) { this.value = v; } } public static class ConcreteCustomType extends AbstractCustomType { public ConcreteCustomType(String v) { super(v); } } public static class AbstractCustomTypeDeserializationConverter extends StdConverter<string abstractcustomtype>{ public AbstractCustomType convert(String arg) { return new ConcreteCustomType(arg); } } public static class AbstractCustomTypeUser { (converter = AbstractCustomTypeDeserializationConverter.class) private final AbstractCustomType customField; AbstractCustomTypeUser(("customField") AbstractCustomType customField) { this.customField = customField; } } public static class NonAbstractCustomType { private final String value; public NonAbstractCustomType(String v) { this.value = v; } } public static class NonAbstractCustomTypeDeserializationConverter extends StdConverter<string nonabstractcustomtype>{ public NonAbstractCustomType convert(String arg) { return new NonAbstractCustomType(arg); } } public static class NonAbstractCustomTypeUser { (converter = NonAbstractCustomTypeDeserializationConverter.class) private final NonAbstractCustomType customField; NonAbstractCustomTypeUser(("customField") NonAbstractCustomType customField) { this.customField = customField; } } private static final ObjectMapper JSON_MAPPER = new ObjectMapper(); public void testAbstractTypeDeserialization() throws IOException { String test="{\"customField\": \"customString\"}"; AbstractCustomTypeUser cu = JSON_MAPPER.readValue(test, AbstractCustomTypeUser.class); assertNotNull(cu); } public void testNonAbstractDeserialization() throws IOException { String test="{\"customField\": \"customString\"}"; NonAbstractCustomTypeUser cu = JSON_MAPPER.readValue(test, NonAbstractCustomTypeUser.class); assertNotNull(cu); } } </string></string></code></pre><p>该提问来源于开源项目:FasterXML/jackson-databind</p></div>
com.fasterxml.jackson.core.JsonParseException: Illegal character ((CTRL-CHAR, code 5)): only regular white space (\r, \n, \t) is allowed between tokens at [Source: �z��&�r#�$�3S"; line: 1, column: 2] at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:1702) ~[jackson-core-2.8.8.jar:2.8.8] at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:558) ~[jackson-core-2.8.8.jar:2.8.8] at com.fasterxml.jackson.core.base.ParserMinimalBase._throwInvalidSpace(ParserMinimalBase.java:509) ~[jackson-core-2.8.8.jar:2.8.8] at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._skipWSOrEnd(ReaderBasedJsonParser.java:2364) ~[jackson-core-2.8.8.jar:2.8.8] at com.fasterxml.jackson.core.json.ReaderBasedJsonParser.nextToken(ReaderBasedJsonParser.java:644) ~[jackson-core-2.8.8.jar:2.8.8] at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:3834) ~[jackson-databind-2.8.8.jar:2.8.8] at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3783) ~[jackson-databind-2.8.8.jar:2.8.8] at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2842) ~[jackson-databind-2.8.8.jar:2.8.8] at io.jsonwebtoken.impl.DefaultJwtParser.readValue(DefaultJwtParser.java:552) ~[jjwt-0.7.0.jar:0.7.0] at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:252) ~[jjwt-0.7.0.jar:0.7.0] at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:481) ~[jjwt-0.7.0.jar:0.7.0] at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:541) ~[jjwt-0.7.0.jar:0.7.0]
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页