1. Introduction
1.绪论
Serializing our complete data structure to JSON using an exact one-on-one representation of all the fields may not be appropriate sometimes or simply may not be what we want. Instead, we may want to create an extended or simplified view of our data. This is where custom Jackson serializers come into play.
使用所有字段的精确一对一表示将我们的完整数据结构序列化为JSON,有时可能并不合适,或者根本不是我们想要的。相反,我们可能希望创建我们数据的扩展或简化视图。这就是自定义 Jackson 序列化器发挥作用的地方。
However, implementing a custom serializer can be tedious, especially if our model objects have lots of fields, collections, or nested objects. Fortunately, the Jackson library has several provisions that can make this job a lot simpler.
然而,实现自定义的序列化器可能很繁琐,尤其是当我们的模型对象有很多字段、集合或嵌套对象时。幸运的是,Jackson 库有几个条款可以使这项工作变得简单得多。
In this short tutorial, we’ll take a look at custom Jackson serializers and show how to access default serializers inside a custom serializer.
在这个简短的教程中,我们将看看自定义的Jackson序列化器,并展示如何在自定义序列化器中访问默认的序列化器。
2. Sample Data Model
2.数据模型样本
Before we dive into the customization of Jackson, let’s have a look at our sample Folder class that we want to serialize:
在我们深入研究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
}
And the File class, which is defined as a List inside our Folder class:
还有文件类,它被定义为我们文件夹类中的列表。
public class File {
private Long id;
private String name;
// standard getters and setters
}
3. Custom Serializers in Jackson
3.Jackson中的自定义序列化器
The main advantage of using custom serializers is that we do not have to modify our class structure. Plus, we can easily decouple our expected behavior from the class itself.
使用自定义序列化器的主要优点是我们不必修改我们的类结构。此外,我们可以轻松地将我们的预期行为与类本身解耦。
So, let’s imagine that we want a reduced view of our Folder class:
所以,让我们想象一下,我们想要一个缩小的Folder类的视图。
{
"name": "Root Folder",
"files": [
{"id": 1, "name": "File 1"},
{"id": 2, "name": "File 2"}
]
}
As we’ll see over the next sections, there are several ways we can achieve our desired output in Jackson.
正如我们在接下来的章节中所看到的,我们有几种方法可以在Jackson中实现我们想要的输出。
3.1. Brute Force Approach
3.1.蛮力方法
First, without using Jackson’s default serializers, we can create a custom serializer in which we do all the heavy lifting ourselves.
首先,不使用Jackson的默认序列化器,我们可以创建一个自定义的序列化器,在其中我们自己完成所有的重任。
Let’s create a custom serializer for our Folder class to achieve this:
让我们为我们的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();
}
}
Thus, we can serialize our Folder class to a reduced view containing only the fields that we want.
因此,我们可以将我们的Folder类序列化为一个缩小的视图,只包含我们想要的字段。
3.2. Using Internal ObjectMapper
3.2.使用内部ObjectMapper
Although custom serializers provide us the flexibility of altering every property in detail, we can make our job easier by reusing Jackson’s default serializers.
尽管自定义序列化器为我们提供了改变每个属性细节的灵活性,但我们可以通过重用Jackson的默认序列化器来使我们的工作更容易。
One way of using the default serializers is to access the internal ObjectMapper class:
使用默认序列化器的一种方法是访问内部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();
}
So, Jackson simply handles the heavy lifting by serializing the List of File objects, and then our output will be the same.
因此,Jackson只是通过序列化List的File对象来处理繁重的工作,然后我们的输出将是相同的。
3.3. Using SerializerProvider
3.3.使用SerializerProvider
Another way of calling the default serializers is to use the SerializerProvider. Therefore, we delegate the process to the default serializer of the type File.
另一种调用默认序列化器的方法是使用SerializerProvider。因此,我们将这个过程委托给File类型的默认序列化器。
Now, let’s simplify our code a little bit with the help of SerializerProvider:
现在,让我们在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();
}
And, just as before, we get the same output.
而且,就像以前一样,我们得到同样的输出。
4. A Possible Recursion Problem
4.一个可能的递归问题
Depending on the use case, we may need to extend our serialized data by including more details for Folder. This might be for a legacy system or an external application to be integrated that we do not have a chance to modify.
根据使用情况,我们可能需要通过为Folder包含更多细节来扩展我们的序列化数据。这可能是为了一个遗留系统或要集成的外部应用程序,我们没有机会修改。
Let’s change our serializer to create a details field for our serialized data to simply expose all the fields of the Folder class:
让我们改变我们的序列化器,为我们的序列化数据创建一个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();
}
This time we get a StackOverflowError exception.
这次我们得到一个StackOverflowError异常。
When we define a custom serializer, Jackson internally overrides the original BeanSerializer instance that is created for the type Folder. Consequently, our SerializerProvider finds the customized serializer every time, instead of the default one, and this causes an infinite loop.
当我们定义一个自定义的序列化器时,Jackson在内部覆盖了为Folder类型创建的原始BeanSerializer实例。因此,我们的 SerializerProvider 每次都会找到自定义的序列器,而不是默认的序列器,这将导致无限循环。
So, how do we solve this problem? We’ll see one usable solution for this scenario in the next section.
那么,我们如何解决这个问题呢?我们将在下一节看到这种情况的一个可用的解决方案。
5. Using BeanSerializerModifier
5.使用BeanSerializerModifier
A possible workaround is using BeanSerializerModifier to store the default serializer for the type Folder before Jackson internally overrides it.
一个可能的解决方法是使用BeanSerializerModifier 为Folder类型存储默认的序列化器,然后Jackson在内部覆盖它。
Let’s modify our serializer and add an extra field — defaultSerializer:
让我们修改我们的序列化器,添加一个额外的字段–defaultSerializer。
private final JsonSerializer<Object> defaultSerializer;
public FolderJsonSerializer(JsonSerializer<Object> defaultSerializer) {
super(Folder.class);
this.defaultSerializer = defaultSerializer;
}
Next, we’ll create an implementation of BeanSerializerModifier to pass the default serializer:
接下来,我们将创建一个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;
}
}
Now, we need to register our BeanSerializerModifier as a module to make it work:
现在,我们需要将我们的BeanSerializerModifier注册为一个模块,使其发挥作用。
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.setSerializerModifier(new FolderBeanSerializerModifier());
mapper.registerModule(module);
Then, we use the defaultSerializer for the details field:
然后,我们将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();
}
Lastly, we may want to remove the files field from the details since we already write it into the serialized data separately.
最后,我们可能想把files字段从details中删除,因为我们已经把它单独写入了序列化的数据。
So, we simply ignore the files field in our Folder class:
因此,我们简单地忽略了files类中的Folder字段。
@JsonIgnore
private List<File> files = new ArrayList<>();
Finally, the problem is solved and we get our expected output as well:
最后,问题得到了解决,我们也得到了预期的输出。
{
"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
}
}
6. Conclusion
6.结语
In this tutorial, we learned how to call default serializers inside a custom serializer in Jackson Library.
在本教程中,我们学习了如何在杰克逊库的自定义序列化器中调用默认的序列化器。
Like always, all the code examples used in this tutorial are available over on GitHub.
像往常一样,本教程中使用的所有代码示例都可以在GitHub上找到。