1. 概述

在这篇文章中,我们将探讨如何控制Jackson在序列化和反序列化过程中对字段的操作。

2. public字段

确保一个字段既可序列化又可反序列化的最简单方法是使其成为public字段。

让我们声明一个简单的类,包含一个公共字段、包内可见字段和私有字段:

public class MyDtoAccessLevel {
    private String stringValue;
    int intValue;
    protected float floatValue;
    public boolean booleanValue;
    // NO setters or getters
}

在类的四个字段中,只有公共的booleanValue会在默认情况下被序列化为JSON:

@Test
public void givenDifferentAccessLevels_whenPublic_thenSerializable() 
  throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();

    MyDtoAccessLevel dtoObject = new MyDtoAccessLevel();

    String dtoAsString = mapper.writeValueAsString(dtoObject);
    assertThat(dtoAsString, not(containsString("stringValue")));
    assertThat(dtoAsString, not(containsString("intValue")));
    assertThat(dtoAsString, not(containsString("floatValue")));

    assertThat(dtoAsString, containsString("booleanValue"));
}

3. 增加getter使非公共字段可序列化和可反序列化

现在,另一种简单的方法是为一个字段(尤其是非公共字段)添加getter:

public class MyDtoWithGetter {
    private String stringValue;
    private int intValue;

    public String getStringValue() {
        return stringValue;
    }
}

我们期望stringValue字段可以被序列化,而其他私有字段则不会,因为它们没有getter:

@Test
public void givenDifferentAccessLevels_whenGetterAdded_thenSerializable() 
  throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();

    MyDtoGetter dtoObject = new MyDtoGetter();

    String dtoAsString = mapper.writeValueAsString(dtoObject);
    assertThat(dtoAsString, containsString("stringValue"));
    assertThat(dtoAsString, not(containsString("intValue")));
}

有趣的是,getter也会使私有字段在反序列化时变得可访问——一旦它有了getter,该字段就被视为属性。

让我们看看它是如何工作的:

@Test
public void givenDifferentAccessLevels_whenGetterAdded_thenDeserializable() 
  throws JsonProcessingException, JsonMappingException, IOException {
    String jsonAsString = "{\"stringValue\":\"dtoString\"}";
    ObjectMapper mapper = new ObjectMapper();
    MyDtoWithGetter dtoObject = mapper.readValue(jsonAsString, MyDtoWithGetter.class);

    assertThat(dtoObject.getStringValue(), equalTo("dtoString"));
}

4. 增加setter仅使非公共字段可反序列化

我们已经看到getter使私有字段既可序列化也可反序列化。相反,setter只会标记非公共字段为可反序列化:

public class MyDtoWithSetter {
    private int intValue;

    public void setIntValue(int intValue) {
        this.intValue = intValue;
    }

    public int accessIntValue() {
        return intValue;
    }
}

如您所见,这次intValue字段只有一个setter。我们确实有一个访问值的方式,但这不是一个标准的getter。

intValue的反序列化过程应该能正常工作:

@Test
public void givenDifferentAccessLevels_whenSetterAdded_thenDeserializable() 
  throws JsonProcessingException, JsonMappingException, IOException {
    String jsonAsString = "{\"intValue\":1}";
    ObjectMapper mapper = new ObjectMapper();

    MyDtoSetter dtoObject = mapper.readValue(jsonAsString, MyDtoSetter.class);

    assertThat(dtoObject.anotherGetIntValue(), equalTo(1));
}

正如我们所说,setter只应使字段可反序列化,但不可序列化:

@Test
public void givenDifferentAccessLevels_whenSetterAdded_thenStillNotSerializable() 
  throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();

    MyDtoSetter dtoObject = new MyDtoSetter();

    String dtoAsString = mapper.writeValueAsString(dtoObject);
    assertThat(dtoAsString, not(containsString("intValue")));
}

5. 全局启用字段序列化

在某些情况下,例如当您无法直接修改源代码时,我们需要从外部配置Jackson处理非公共字段的方式。

这种全局配置可以在ObjectMapper级别完成,通过开启AutoDetect功能,使用公有字段或getter/setter方法进行序列化,或者开启所有字段的序列化:

ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);

以下测试用例验证了MyDtoAccessLevel的所有成员字段(包括非公共字段)都是可序列化的:

@Test
public void givenDifferentAccessLevels_whenSetVisibility_thenSerializable() 
  throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();
    mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);

    MyDtoAccessLevel dtoObject = new MyDtoAccessLevel();

    String dtoAsString = mapper.writeValueAsString(dtoObject);
    assertThat(dtoAsString, containsString("stringValue"));
    assertThat(dtoAsString, containsString("intValue"));
    assertThat(dtoAsString, containsString("booleanValue"));
}

6. 控制字段与JSON的映射和反向映射

除了控制哪些字段被序列化或反序列化外,你还可以控制字段如何映射到JSON并反向映射回来。我在这篇文章中详细介绍了这种配置。

7. 序列化或反序列化时忽略字段

参考教程,我们有一篇指南,说明如何完全忽略序列化和反序列化过程中的字段。

然而,有时我们只想在其中一边忽略字段,而不是两边都忽略。Jackson足够灵活,可以处理这种有趣的用例。

以下示例显示了一个包含敏感密码信息的User对象,不应将这些信息序列化为JSON。

为了实现这一点,我们在password的getter上添加@JsonIgnore注解,并通过在setter上应用@JsonProperty注解来启用字段的反序列化:

@JsonIgnore
public String getPassword() {
    return password;
}
@JsonProperty
public void setPassword(String password) {
    this.password = password;
}

现在,密码信息不会被序列化为JSON:

@Test
public void givenFieldTypeIsIgnoredOnlyAtSerialization_whenUserIsSerialized_thenIgnored() 
  throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();

    User userObject = new User();
    userObject.setPassword("thePassword");

    String userAsString = mapper.writeValueAsString(userObject);
    assertThat(userAsString, not(containsString("password")));
    assertThat(userAsString, not(containsString("thePassword")));
}

但是,包含密码的JSON将成功反序列化为User对象:

@Test
public void givenFieldTypeIsIgnoredOnlyAtSerialization_whenUserIsDeserialized_thenCorrect() 
  throws JsonParseException, JsonMappingException, IOException {
    String jsonAsString = "{\"password\":\"thePassword\"}";
    ObjectMapper mapper = new ObjectMapper();

    User userObject = mapper.readValue(jsonAsString, User.class);

    assertThat(userObject.getPassword(), equalTo("thePassword"));
}

8. 总结

本教程概述了如何控制Jackson在序列化/反序列化过程中选择哪些字段以及如何完全控制这个过程。你也可以通过深入学习相关文章,如忽略字段将JSON数组反序列化为Java数组或集合,进一步理解Jackson 2的工作原理。

所有这些示例和代码片段的实现可以在我的GitHub项目中找到:[https://github.com/eugenp/tutorials/tree/master/jackson-modules/jackson-conversions#readme]**](https://github.com/eugenp/tutorials/tree/master/jackson-modules/jackson-conversions#readme)**——这是一个基于Eclipse的项目,可以直接导入并运行,无需修改。