1. 概述

在本篇教程中,我们将介绍两种使用 Jackson JSON 处理库来反序列化不可变 Java 对象的方式。

2. 为什么使用不可变对象?

不可变对象(Immutable Object) 是指从创建之初起其状态就不再发生变化的对象。也就是说,无论调用该对象的哪个方法,它的行为始终保持一致。

在设计多线程环境下的系统时,不可变对象非常有用,因为它们天然具备线程安全特性。

此外,在处理外部输入数据时(如用户输入或从存储中读取的数据),不可变对象也非常重要,因为我们需要保护这些数据不被意外修改

接下来我们看看如何对不可变对象进行反序列化。

3. 使用公共构造函数

Employee 类为例,它包含两个必填字段:idname。因此我们可以定义一个全参数公共构造函数,其参数与对象字段一一对应:

public class Employee {

    private final long id;
    private final String name;

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

    // getters
}

这样,在对象创建时就能完成所有字段的初始化。由于字段使用了 final 修饰符,后续也无法修改其值。

为了让 Jackson 能够反序列化这个类,我们只需为构造函数添加几个 注解

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

我们来详细解释一下这些注解的作用。

首先,@JsonCreator 告诉 Jackson 反序列化器应该使用这个构造函数来进行反序列化。

该注解支持两种模式:PROPERTIESDELEGATING
✅ 当我们使用全参数构造函数时,推荐使用 PROPERTIES 模式;
⚠️ 而 DELEGATING 更适用于单参数构造函数。

然后,我们需要为每个构造函数参数加上 @JsonProperty 注解,并指定对应的属性名。这一步要特别注意,属性名必须与序列化时使用的名称完全一致

下面是一个简单的单元测试示例,用于验证 Employee 对象的反序列化:

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

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

4. 使用私有构造函数和 Builder 模式

有时候对象会包含一些可选字段。例如,Person 类有一个可选的 age 字段:

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

    // getters
}

当字段较多且存在大量可选字段时,直接使用构造函数会导致参数膨胀,代码也会变得臃肿难读。

这时候就可以借助经典的 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);
    } 
}

为了让 Jackson 能够识别并使用这个 Builder,我们需要添加两个注解:

  • 在类上使用 @JsonDeserialize(builder = Person.Builder.class),指明 Builder 类的完整类名;
  • 在 Builder 类上使用 @JsonPOJOBuilder 注解。
@JsonDeserialize(builder = Person.Builder.class)
public class Person {
    //...
    
    @JsonPOJOBuilder
    static class Builder {
        //...
    }
}

💡 注:你可以自定义 Builder 中的方法命名规则。

  • buildMethodName 默认是 "build",表示最终构建对象的方法名;
  • withPrefix 默认是 "with",表示设置属性的方法前缀。

所以我们在上面的例子中没有显式指定这两个参数。

下面是用于测试 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. 总结

在这篇文章中,我们介绍了如何使用 Jackson 库来反序列化不可变对象。

文中提到的所有代码都可以在 GitHub 上找到。


原始标题:Deserialize Immutable Objects with Jackson