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的项目,可以直接导入并运行,无需修改。