Jackson 部分序列化

1. 概述

有时候,在序列化/反序列化某个对象时,我们只想要序列化/反序列化部分的字段,而不是全部。在本文中,我们将探讨 如何控制Jackson序列化/反序列化范围的各种方法

2. public Field

确保字段可序列化和可反序列化的最简单方法是将其可见属性设置为 public,如下所示,我们声明一个带有 publicpackageprivate 的简单类(不需要 getter/setter 的支持):

1
2
3
4
5
6
7
public class MyDtoAccessLevel {
private String stringValue;
int intValue;
protected float floatValue;
public boolean booleanValue;
// NO setters or getters
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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 使 非 public 字段可序列化和可反序列化

如果一个字段是 非 public 的,又想其支持序列化和反序列化,那么最简单的方法是给其添加一个 Getter 方法:

1
2
3
4
5
6
7
8
public class MyDtoWithGetter {
private String stringValue;
private int intValue;

public String getStringValue() {
return stringValue;
}
}

上例中我们给 stringValue 添加了一个 Getter 方法,而 intValue 没有,我们看看序列化的结果:

1
2
3
4
5
6
7
8
9
10
11
@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")));
}

可以看到序列化后的数据中包含了 stringValue 而没有 intValue ,我们再看看反序列化的情况:

1
2
3
4
5
6
7
8
9
@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"));
}

结果证明,添加了 Getter 的字段同时具备的了序列化和反序列化的能力。

4. Setter 只使 非 public 字段支持反序列化

在上一节展示了 Getter 如何使私有字段既可序列化又可反序列化,这一节讨论一下 Setter 对于序列化/反序列化的影响:

1
2
3
4
5
6
7
8
9
10
11
public class MyDtoWithSetter {
private int intValue;

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

public int accessIntValue() {
return intValue;
}
}

上述代码中,intValue 字段此次只有一个 setter,我们测试一下序列化和反序列化操作后的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@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));
}

@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")));
}

上面的单元测试也表明:setter 只使得字段可反序列化,但不能序列化。

1
2
3
4
5
6
7
8
9
10
11

## 5. 使所有字段全局可序列化

有些时候,JavaBean并不是我们自己编码的,而是来自第三方,我们无法控制其源码(无法修改其字段的可见性,也无法添加 *Getter/Setter*),这个时候该如何控制字段的序列化策略呢。

答案是在 *ObjectMapper* 上进行全局配置,如下所示:

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

测试用例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@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.序列化/反序列化时忽略某些字段

有时我们只需要在序列化/反序列化时忽略一些字段,这个时候就可以使用 @JsonIgnore 注解,它用于标记在序列化/反序列化时,忽略掉该字段:

1
2
3
4
5
6
7
8
9
@JsonIgnore
public String getPassword() {
return password;
}

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

对应的测试用例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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")));
}

@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"));
}

可以看到,password 字段并没有被序列化,而由于 @JsonProperty 注解的存在,在反序列化时,仍然会将数据设置到 password 字段中去。