Сериализация нашей полной структуры данных в JSON с использованием точного представления всех полей один на один иногда может быть неуместной или просто может быть не тем, что мы хотим. Вместо этого мы можем захотеть создать расширенное или упрощенное представление наших данных. Именно здесь в игру вступают пользовательские сериализаторы Джексона.
Однако реализация пользовательского сериализатора может быть утомительной, особенно если объекты нашей модели содержат множество полей, коллекций или вложенных объектов. К счастью, в библиотеке Джексона есть несколько положений, которые могут значительно упростить эту работу.
В этом коротком уроке мы рассмотрим пользовательский сериализатор Джексона и покажем , как получить доступ к сериализаторам по умолчанию внутри пользовательского сериализатора .
2. Пример Модели Данных
Прежде чем мы погрузимся в настройку Джексона, давайте взглянем на наш пример Папка класс, который мы хотим сериализовать:
public class Folder {
private Long id;
private String name;
private String owner;
private Date created;
private Date modified;
private Date lastAccess;
private List files = new ArrayList<>();
// standard getters and setters
}
И Файл класс, который определяется как Список внутри нашего Папки класса:
public class File {
private Long id;
private String name;
// standard getters and setters
}
3. Пользовательские сериализаторы в Джексоне
Основное преимущество использования пользовательских сериализаторов заключается в том, что нам не нужно изменять структуру классов. Кроме того, мы можем легко отделить наше ожидаемое поведение от самого класса.
Итак, давайте представим, что нам нужно уменьшенное представление нашего класса Folder :
Как мы увидим в следующих разделах, есть несколько способов добиться желаемого результата в Джексоне.
3.1. Подход Грубой Силы
Во-первых, без использования сериализаторов Джексона по умолчанию мы можем создать пользовательский сериализатор, в котором мы сами выполняем всю тяжелую работу.
Давайте создадим пользовательский сериализатор для нашего класса Folder , чтобы достичь этого:
public class FolderJsonSerializer extends StdSerializer {
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
Хотя пользовательские сериализаторы предоставляют нам гибкость в изменении каждого свойства в деталях, мы можем упростить нашу работу, повторно используя сериализаторы Джексона по умолчанию.
Одним из способов использования сериализатора по умолчанию является доступ к внутреннему классу ObjectMapper :
В зависимости от варианта использования нам может потребоваться расширить наши сериализованные данные, включив дополнительные сведения для Папки . Это может быть для устаревшей системы или внешнего приложения, которое будет интегрировано, и у нас нет возможности изменить .
Давайте изменим наш сериализатор, чтобы создать поле 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 .
Когда мы определяем пользовательский сериализатор, Джексон внутренне переопределяет исходныйBeanSerializerэкземпляр , созданный для типа Папка . Следовательно, наш SerializerProvider каждый раз находит настроенный сериализатор вместо стандартного, и это приводит к бесконечному циклу.
Итак, как мы решаем эту проблему? Мы увидим одно полезное решение для этого сценария в следующем разделе.
5. Использование BeanSerializerModifier
Возможным обходным путем является использование BeanSerializerModifierдля хранения сериализатора по умолчанию для типа Папки | до того, как Джексон внутренне переопределит его.
Давайте изменим наш сериализатор и добавим дополнительное поле — defaultSerializer :
private final JsonSerializer
Затем мы создадим реализацию 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) serializer);
}
return serializer;
}
}
Теперь нам нужно зарегистрировать наш BeanSerializerModifier в качестве модуля, чтобы он работал:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.setSerializerModifier(new FolderBeanSerializerModifier());
mapper.registerModule(module);
Затем мы используем сериализатор по умолчанию для поля сведения :