Using Optional with Jackson


在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();
}

单元测试成功!


文章作者: shiv
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 shiv !
评论
  目录