Using Optional with Jackson – 与杰克逊一起使用可选择的方式

最后修改: 2017年 2月 12日

 

1. Introduction

1.介绍

In this article, we’ll give an overview of the Optional class, and then explain some problems that we might run into when using it with Jackson.

在这篇文章中,我们将对Optional类进行概述,然后解释一些我们在与Jackson一起使用它时可能遇到的问题。

Following this, we’ll introduce a solution which will get Jackson to treat Optionals as if they were ordinary nullable objects.

在这之后,我们将介绍一个解决方案,它将使Jackson把Optionals当作普通的nullable对象。

2. Problem Overview

2.问题概述

First, let’s take a look at what happens when we try to serialize and deserialize Optionals with Jackson.

首先,让我们看看当我们试图用Jackson对Optionals进行序列化和反序列化时会发生什么。

2.1. Maven Dependency

2.1.Maven的依赖性

To use Jackson, let’s make sure we’re using its latest version:

要使用Jackson,让我们确保我们使用的是其最新版本

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.13.3</version>
</dependency>

2.2. Our Book Object

2.2.我们的图书对象

Then, let’s create a class Book, containing one ordinary and one Optional field:

然后,让我们创建一个Book类,包含一个普通的和一个Optional字段。

public class Book {
   String title;
   Optional<String> subTitle;
   
   // getters and setters omitted
}

Keep in mind that Optionals should not be used as fields and we are doing this to illustrate the problem.

请记住,Optionals不应该作为字段使用,我们这样做是为了说明问题。

2.3. Serialization

2.3.序列化

Now, let’s instantiate a Book:

现在,让我们实例化一个Book

Book book = new Book();
book.setTitle("Oliver Twist");
book.setSubTitle(Optional.of("The Parish Boy's Progress"));

And finally, let’s try serializing it using a Jackson ObjectMapper:

最后,让我们试着用Jackson的ObjectMapper将其序列化。

String result = mapper.writeValueAsString(book);

We’ll see that the output of the Optional field, does not contain its value, but instead a nested JSON object with a field called present:

我们将看到,Optional字段的输出,并不包含它的值,而是一个嵌套的JSON对象,其中有一个叫做present的字段。

{"title":"Oliver Twist","subTitle":{"present":true}}

Although this may look strange, it’s actually what we should expect.

虽然这可能看起来很奇怪,但实际上是我们应该期待的。

In this case, isPresent() is a public getter on the Optional class. This means it will be serialized with a value of true or false, depending on whether it is empty or not. This is Jackson’s default serialization behavior.

在这种情况下,isPresent() Optional 类的一个公共获取器。这意味着它将以truefalse的值被序列化,这取决于它是否为空。这是Jackson的默认序列化行为。

If we think about it, what we want is for actual the value of the subtitle field to be serialized.

如果我们考虑一下,我们想要的是让副标题字段的实际值被序列化。

2.4. Deserialization

2.4.反序列化

Now, let’s reverse our previous example, this time trying to deserialize an object into an Optional. We’ll see that now we get a JsonMappingException:

现在,让我们反过来看看我们之前的例子,这次我们试图将一个对象反序列化为一个Optional。我们将看到,现在我们得到一个JsonMappingException

@Test(expected = JsonMappingException.class)
public void givenFieldWithValue_whenDeserializing_thenThrowException
    String bookJson = "{ \"title\": \"Oliver Twist\", \"subTitle\": \"foo\" }";
    Book result = mapper.readValue(bookJson, Book.class);
}

Let’s view the stack trace:

让我们查看一下堆栈跟踪。

com.fasterxml.jackson.databind.JsonMappingException:
  Can not construct instance of java.util.Optional:
  no String-argument constructor/factory method to deserialize from String value ('The Parish Boy's Progress')

This behavior again makes sense. Essentially, Jackson needs a constructor which can take the value of subtitle as an argument. This is not the case with our Optional field.

这种行为又有了意义。从本质上讲,Jackson需要一个构造函数,它可以将subtitle的值作为一个参数。我们的Optional字段不是这样的。

3. Solution

3.解决方案

What we want, is for Jackson to treat an empty Optional as null, and to treat a present Optional as a field representing its value.

我们想要的是,Jackson将一个空的Optional视为null,并将一个存在的Optional视为代表其价值的字段。

Fortunately, this problem has been solved for us. Jackson has a set of modules that deal with JDK 8 datatypes, including Optional.

幸运的是,这个问题已经为我们解决了。Jackson有一组处理JDK 8数据类型的模块,包括Optional

3.1. Maven Dependency and Registration

3.1.Maven的依赖性和注册

First, let’s add the latest version as a Maven dependency:

首先,让我们把最新的版本作为Maven的依赖项加入。

<dependency>
   <groupId>com.fasterxml.jackson.datatype</groupId>
   <artifactId>jackson-datatype-jdk8</artifactId>
   <version>2.13.3</version>
</dependency>

Now, all we need to do is register the module with our ObjectMapper:

现在,我们需要做的是在我们的ObjectMapper上注册该模块。

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jdk8Module());

3.2. Serialization

3.2.序列化

Now, let’s test it. If we try and serialize our Book object again, we’ll see that there is now a subtitle, as opposed to a nested JSON:

现在,让我们来测试它。如果我们再次尝试序列化我们的对象,我们会看到现在有一个副标题,而不是一个嵌套的JSON。

Book book = new Book();
book.setTitle("Oliver Twist");
book.setSubTitle(Optional.of("The Parish Boy's Progress"));
String serializedBook = mapper.writeValueAsString(book);
 
assertThat(from(serializedBook).getString("subTitle"))
  .isEqualTo("The Parish Boy's Progress");

If we try serializing an empty book, it will be stored as null:

如果我们尝试序列化一个空书,它将被存储为null

book.setSubTitle(Optional.empty());
String serializedBook = mapper.writeValueAsString(book);
 
assertThat(from(serializedBook).getString("subTitle")).isNull();

3.3. Deserialization

3.3.反序列化

Now, let’s repeat our tests for deserialization. If we reread our Book, we’ll see that we no longer get a JsonMappingException:

现在,让我们重复一下我们对反序列化的测试。如果我们重读我们的书,我们会看到我们不再得到一个JsonMappingException:

Book newBook = mapper.readValue(result, Book.class);
 
assertThat(newBook.getSubTitle()).isEqualTo(Optional.of("The Parish Boy's Progress"));

Finally, let’s repeat the test again, this time with null. We’ll see that yet again we don’t get a JsonMappingException, and in fact, have an empty Optional:

最后,让我们再次重复测试,这次是用null。我们将再次看到,我们没有得到一个JsonMappingException,事实上,有一个空的Optional:

assertThat(newBook.getSubTitle()).isEqualTo(Optional.empty());

4. Conclusion

4.结论

We’ve shown how to get around this problem by leveraging the JDK 8 DataTypes module, demonstrating how it enables Jackson to treat an empty Optional as null, and a present Optional as an ordinary field.

我们已经展示了如何通过利用JDK 8 DataTypes模块来解决这个问题,演示了它如何使Jackson将一个空的Optional视为null,而将一个存在的Optional视为一个普通字段。

The implementation of these examples can be found over on GitHub; this is a Maven-based project, so should be easy to run as is.

这些例子的实现可以在GitHub上找到;这是一个基于Maven的项目,所以应该很容易按原样运行。