1. 简介
将我们完整的数据结构序列化为JSON时使用所有字段的精确一对一表示有时可能不合适,或者根本可能不是我们想要的。 相反,我们可能想要创建数据的扩展或简化视图。 这就是自定义 Jackson 序列化程序发挥作用的地方。
但是,实现自定义序列化程序可能很乏味,尤其是当我们的模型对象有很多字段、集合或嵌套对象时。 幸运的是,
Jackson 库
有几种方式可以让这项工作变得更简单。
在本文中,我们将看看自定义 Jackson 序列化器,并展示如何在自定义序列化器中访问默认序列化器。
2. 示例数据模型
在深入研究 Jackson 的自定义序列化器之前,让我们先看看我们想要序列化的示例
Folder
类:
public class Folder {
private Long id;
private String name;
private String owner;
private Date created;
private Date modified;
private Date lastAccess;
private List<File> files = new ArrayList<>();
// standard getters and setters
}
还有
File
类,它在我们的
Folder
类中定义为
List
:
public class File {
private Long id;
private String name;
// standard getters and setters
}
3. Jackson中的自定义序列化器
使用自定义序列化器的主要优点是我们不必修改我们的类结构。 另外,我们可以轻松地将我们的预期行为与类本身分离。
所以,让我们想象一下,我们想要一个
Folder
类的简化视图:
{
"name": "Root Folder",
"files": [
{"id": 1, "name": "File 1"},
{"id": 2, "name": "File 2"}
]
}
正如我们将在接下来的部分中看到的,有几种方法可以在 Jackson 中实现我们想要的输出。
3.1. 蛮力方法
首先,不使用 Jackson 的默认序列化器,我们可以创建一个自定义序列化器,我们自己完成所有繁重的工作。
让我们为我们的
Folder
类创建一个自定义序列化器来实现这一点:
public class FolderJsonSerializer extends StdSerializer<Folder> {
public FolderJsonSerializer() {
super(Folder.class);
}
@Override
public void serialize(Folder value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
gen.writeStartObject();
gen.writeStringField("name", value.getName());
gen.writeArrayFieldStart("files");
for (File file : value.getFiles()) {
gen.writeStartObject();
gen.writeNumberField("id", file.getId());
gen.writeStringField("name", file.getName());
gen.writeEndObject();
}
gen.writeEndArray();
gen.writeEndObject();
}
}
因此,我们可以将我们的
Folder
类序列化为仅包含我们想要的字段的简化视图。
3.2. 使用内在的ObjectMapper
尽管自定义序列化器为我们提供了详细更改每个属性的灵活性,但我们可以通过重用 Jackson 的默认序列化器来简化工作。
使用默认序列化器的一种方法是访问内在的
ObjectMapper
类:
@Override
public void serialize(Folder value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeStringField("name", value.getName());
ObjectMapper mapper = (ObjectMapper) gen.getCodec();
gen.writeFieldName("files");
String stringValue = mapper.writeValueAsString(value.getFiles());
gen.writeRawValue(stringValue);
gen.writeEndObject();
}
因此,Jackson 只是通过序列化
File
对象的
List
来处理繁重的工作,然后我们的输出将是相同的。
3.3. 使用 SerializerProvider
调用默认序列化器的另一种方法是使用
SerializerProvider
。因此,我们将过程委托给
File
类型的默认序列化程序。
现在,让我们在
SerializerProvider
的帮助下稍微简化一下我们的代码:
@Override
public void serialize(Folder value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeStringField("name", value.getName());
provider.defaultSerializeField("files", value.getFiles(), gen);
gen.writeEndObject();
}
而且,就像以前一样,我们得到相同的输出。
4. 一个可能的递归问题
根据用例,我们可能需要通过包含
Folder
的更多详细信息来扩展我们的序列化数据。 这可能是为了集成一个遗留系统或一个我们没有机会修改的外部应用程序。
让我们更改我们的序列化器,为我们的序列化数据创建一个
details
字段,以简单地公开
Folder
类的所有字段:
@Override
public void serialize(Folder value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeStringField("name", value.getName());
provider.defaultSerializeField("files", value.getFiles(), gen);
// this line causes exception
provider.defaultSerializeField("details", value, gen);
gen.writeEndObject();
}
这次我们得到一个
StackOverflowError
异常。
当我们定义一个自定义序列化器时,Jackson 在内部覆盖了为
Folder
类型创建的原始
BeanSerializer
实例。因此,我们的
SerializerProvider
每次都会找到自定义的序列化器,而不是默认的序列化器,这会导致无限循环。
那么,我们如何解决这个问题呢? 我们将在下一节中看到适用于这种情况的一个可用解决方案。
5. 使用 BeanSerializerModifier
一种可能的解决方法是使用
BeanSerializerModifier
在 Jackson 内部覆盖它之前存储
Folder
类型的默认序列化程序。
让我们修改我们的序列化器并添加一个额外的字段 –
defaultSerializer
:
private final JsonSerializer<Object> defaultSerializer;
public FolderJsonSerializer(JsonSerializer<Object> defaultSerializer) {
super(Folder.class);
this.defaultSerializer = defaultSerializer;
}
接下来,我们将创建一个
BeanSerializerModifier
的实现来传递默认的序列化器:
public class FolderBeanSerializerModifier extends BeanSerializerModifier {
@Override
public JsonSerializer<?> modifySerializer(
SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
if (beanDesc.getBeanClass().equals(Folder.class)) {
return new FolderJsonSerializer((JsonSerializer<Object>) serializer);
}
return serializer;
}
}
现在,我们需要将
BeanSerializerModifier
注册为模块以使其工作:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.setSerializerModifier(new FolderBeanSerializerModifier());
mapper.registerModule(module);
然后,我们将
defaultSerializer
用于
details
字段:
@Override
public void serialize(Folder value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeStringField("name", value.getName());
provider.defaultSerializeField("files", value.getFiles(), gen);
gen.writeFieldName("details");
defaultSerializer.serialize(value, gen, provider);
gen.writeEndObject();
}
最后,我们可能希望从
details
中删除
files
字段,因为我们已经将它单独写入序列化数据中。
因此,我们只需忽略
Folder
类中的
files
字段:
@JsonIgnore
private List<File> files = new ArrayList<>();
最后,问题解决了,我们也得到了预期的输出:
{
"name": "Root Folder",
"files": [
{"id": 1, "name": "File 1"},
{"id": 2, "name": "File 2"}
],
"details": {
"id":1,
"name": "Root Folder",
"owner": "root",
"created": 1565203657164,
"modified": 1565203657164,
"lastAccess": 1565203657164
}
}
<<<<<<<<<<<< [完] >>>>>>>>>>>>