Deserialize Immutable Objects with Jackson – 用Jackson反序列化不可变的对象

最后修改: 2019年 1月 14日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this quick tutorial, we’ll show two different ways of deserializing immutable Java objects with the Jackson JSON processing library.

在这个快速教程中,我们将展示使用Jackson JSON处理库对不可变的Java对象进行反序列化的两种不同方式。

2. Why Do We Use Immutable Objects?

2.为什么我们要使用不可变的对象?

An immutable object is an object that keeps its state intact since the very moment of its creation. It means that no matter which methods of the object the end user calls, the object behaves the same way.

immutable 对象是一个对象,自其创建的那一刻起就保持其状态不变。这意味着,无论最终用户调用该对象的哪些方法,该对象的行为都是一样的

Immutable objects come in handy when we design a system that must work in a multithreaded environment, as immutability generally guarantees thread safety.

当我们设计一个必须在多线程环境下工作的系统时,不可变的对象就会派上用场,因为不可变性通常能保证线程安全。

On the other hand, immutable objects are useful when we need to handle input from external sources. For instance, it can be user input or some data from storage. In that case, it may be critical to preserve the received data and protect it from accidental or unintended changes.

另一方面,当我们需要处理来自外部的输入时,不可变的对象很有用。例如,它可以是用户输入或来自存储的一些数据。在这种情况下,保存收到的数据并保护其免受意外或非故意的改变可能是至关重要的。

Let’s see how we can deserialize an immutable object.

让我们看看我们如何反序列化一个不可变的对象。

3. Public Constructor

3.公共构造函数

Let’s consider the Employee class structure. It has two required fields: id and name, thus we define a public all-arguments constructor that has a set of arguments that matches the set of object’s fields:

让我们考虑一下Employee类结构。它有两个必填字段。idname,因此我们定义了一个public all-arguments constructor,它的参数集与对象的字段集一致。

public class Employee {

    private final long id;
    private final String name;

    public Employee(long id, String name) {
        this.id = id;
        this.name = name;
    }

    // getters
}

This way, we’ll have all the object’s fields initialized at the moment of creation. Final modifiers in fields’ declaration won’t let us change their values in future. To make this object deserializable, we simply need to add a couple of annotations to this constructor:

这样一来,我们将在创建的那一刻初始化所有对象的字段。字段声明中的最终修饰符不会让我们在将来改变它们的值。为了使这个对象可以反序列化,我们只需要在这个构造函数中添加几个注释

@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public Employee(@JsonProperty("id") long id, @JsonProperty("name") String name) {
    this.id = id;
    this.name = name;
}

Let’s take a closer look at the annotations we have just added.

让我们仔细看看我们刚刚添加的注释。

First of all, @JsonCreator tells Jackson deserializer to use the designated constructor for deserialization.

首先,@JsonCreator告诉Jackson反序列化器使用指定的构造器进行反序列化

There are two modes that can be used as a parameter for this annotation – PROPERTIES and DELEGATING.

有两种模式可以作为该注释的参数–PROPERTIESDELEGATING

PROPERTIES is the most suitable when we declare an all-arguments constructor, while DELEGATING may be useful for single-argument constructors.

当我们声明一个全参数构造函数时,PROPERTIES是最合适的,而DELEGATING可能对单参数构造函数有用。

After that, we need to annotate each of constructor arguments with @JsonProperty stating the name of the respective property as the annotation value. We should be very careful at this step, as all the property names must match with the ones that we used during serialization.

之后,我们需要用@JsonProperty来注解每个构造器参数,说明各自的属性名称作为注解值。在这一步我们应该非常小心,因为所有的属性名必须与我们在序列化过程中使用的属性名相匹配

Let’s take a look at a simple unit test that covers the deserialization of an Employee object:

让我们来看看一个简单的单元测试,它涵盖了一个Employee对象的反序列化。

String json = "{\"name\":\"Frank\",\"id\":5000}";
Employee employee = new ObjectMapper().readValue(json, Employee.class);

assertEquals("Frank", employee.getName());
assertEquals(5000, employee.getId());

4. Private Constructor and a Builder

4.私有构造函数和生成器

Sometimes it happens that an object has a set of optional fields. Let’s consider another class structure, Person, which has an optional age field:

有时会发生这样的情况:一个对象有一组可选字段。让我们考虑另一个类结构,Person,它有一个可选的age字段。

public class Person {
    private final String name;
    private final Integer age;

    // getters
}

When we have a significant number of such fields, creating a public constructor may become cumbersome. In other words, we’ll need to declare a lot of arguments for the constructor and annotate each of them with @JsonProperty annotations. As a result, many repetitive declarations will make our code bloated and hard to read.

当我们有大量这样的字段时,创建一个公共构造函数可能会变得很麻烦。换句话说,我们需要为构造函数声明很多参数,并为每个参数加上@JsonProperty注解。结果,许多重复的声明会使我们的代码变得臃肿,难以阅读。

This is the case when a classical Builder pattern comes to the rescue. Let’s see how we can employ its power in deserialization. First of all, let’s declare a private all-arguments constructor and a Builder class:

在这种情况下,经典的Builder模式就会发挥作用。让我们看看我们如何在反序列化中运用它的力量。首先,让我们声明一个私有的全参数构造函数和一个Builder

private Person(String name, Integer age) {
    this.name = name;
    this.age = age;
}

static class Builder {
    String name;
    Integer age;
    
    Builder withName(String name) {
        this.name = name;
        return this;
    }
    
    Builder withAge(Integer age) {
        this.age = age;
        return this;
    }
    
    public Person build() {
        return new Person(name, age);
    } 
}

To make the Jackson deserializer use this Builder, we just need to add two annotations to our code. First of all, we need to mark our class with @JsonDeserialize annotation, passing a builder parameter with a fully qualified domain name of a builder class.

为了让Jackson反序列化器使用这个Builder,我们只需要在我们的代码中添加两个注解。首先,我们需要用@JsonDeserialize注解来标记我们的类,传递一个builder参数,其中包含一个构建器类的完全合格域名

After that, we need to annotate the builder class itself as @JsonPOJOBuilder:

之后,我们需要将构建器类本身注释为@JsonPOJOBuilder

@JsonDeserialize(builder = Person.Builder.class)
public class Person {
    //...
    
    @JsonPOJOBuilder
    static class Builder {
        //...
    }
}

Note, that we can customize names of methods used during the build.

注意,我们可以自定义构建过程中使用的方法的名称。

Parameter buildMethodName defaults to “build” and stands for the name of the method that we call when the builder is ready to generate a new object.

参数buildMethodName默认为”build”,代表当构建器准备生成一个新对象时我们调用的方法的名称

Another parameter, withPrefix, stands for the prefix that we add to builder methods responsible for setting properties. The default value for this parameter is “with”. That’s why we didn’t specify any of these parameters in the example.

另一个参数,withPrefix,代表了我们添加到负责设置属性的构建器方法中的前缀。这个参数的默认值是“with”。这就是为什么我们在例子中没有指定这些参数的原因。

Let’s take a look at a simple unit test that covers the deserialization of a Person object:

让我们看看一个简单的单元测试,它涵盖了一个Person对象的反序列化。

String json = "{\"name\":\"Frank\",\"age\":50}";
Person person = new ObjectMapper().readValue(json, Person.class);

assertEquals("Frank", person.getName());
assertEquals(50, person.getAge().intValue());

5. Conclusion

5.总结

In this short article, we’ve seen how to deserialize immutable objects using the Jackson library.

在这篇短文中,我们看到了如何使用Jackson库来反序列化不可变的对象。

All the code related to this article can be found over on GitHub.

与本文有关的所有代码都可以在GitHub上找到。