在java8的时候,针对NOP异常,提供了Optional的解决方案。但是你了解Jackson序列化含Optional字段的POJO时会发生什么吗?在本文中,将介绍Optional类,然后解释在与Jackson一起使用时可能遇到的一些问题。接下来,我们将介绍一个解决方案,让Jackson将Optional视为普通的可空对象。
1 引入Jackson
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.6</version>
</dependency>
2 实体类
public class User {
private String username;
private Optional<String> nickName;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Optional<String> getNickName() {
return nickName;
}
public void setNickName(Optional<String> nickName) {
this.nickName = nickName;
}
}
请记住,Optionals不应该用作属性,这里这样做是为了说明问题。
3 序列化
public class JacksonTest {
private ObjectMapper mapper;
@Before
public void init() {
mapper = new ObjectMapper();
}
@Test
public void test1() throws JsonProcessingException {
User user = new User();
user.setUsername("SHIV");
user.setNickName(Optional.of("JACK"));
String result = mapper.writeValueAsString(user);
System.out.println(result);
}
}
输出结果:
{"username":"SHIV","nickName":{"present":true}}
虽然这可能看起来很奇怪,但是这就是实际的结果。在这种情况下,isPresent()是Optional类的公共getter 。这意味着它将使用值true或false进行序列化,具体取决于它是否为空。这是Jackson的默认序列化行为。但是通常这不是我们想要的结果,我们需要的是nickName的实际值”JACK”。
4 反序列化
让我们将上面的输出结果反序列化一下,看看结果:
@Test
public void test2() throws IOException {
String json = "{\"username\":\"SHIV\",\"nickName\":{\"present\":true}}";
User result = mapper.readValue(json, User.class);
}
发生异常,输出结果:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "present" (class java.util.Optional), not marked as ignorable (0 known properties: ])
at [Source: (String)"{"username":"SHIV","nickName":{"present":true}}"; line: 1, column: 46] (through reference chain: cn.justme.lockdemo.User["nickName"]->java.util.Optional["present"])
at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:61)
...
jackson无法识别present字段.
如果用String json = "{\"username\":\"SHIV\",\"nickName\":\"JACK\"}";
来反序列化,依然不会成功。
5 解决方案
我们想要的是让Jackson将空的Optional视为null,并将当前的Optional视为表示其值的字段。幸运的是,这个问题已经解决了。Jackson有一组处理JDK 8数据类型的模块,包括Optional。
5.1 maven依赖
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>2.9.6</version>
</dependency>
接着,我们需要做的就是使用ObjectMapper注册模块
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jdk8Module());
5.2 序列化
public class Common1 {
private ObjectMapper mapper;
@Before
public void init() {
mapper = new ObjectMapper();
mapper.registerModule(new Jdk8Module());
}
@Test
public void test1() throws JsonProcessingException {
User user = new User();
user.setUsername("SHIV");
user.setNickName(Optional.of("JACK"));
String result = mapper.writeValueAsString(user);
System.out.println(result);
user.setNickName(Optional.empty());
String result1 = mapper.writeValueAsString(user);
System.out.println(result1);
}
}
输出结果
{"username":"SHIV","nickName":"JACK"}
{"username":"SHIV","nickName":null}
5.4 反序列化
@Test
public void test2() throws IOException {
String json1 = "{\"username\":\"SHIV\",\"nickName\":\"JACK\"}";
User result1 = mapper.readValue(json1, User.class);
assertThat(result1.getNickName()).isEqualTo(Optional.of("JACK"));
String json2 = "{\"username\":\"SHIV\",\"nickName\":null}";
User result2 = mapper.readValue(json2, User.class);
assertThat(result2.getNickName()).isEmpty();
}
单元测试成功!