Рубрики
Без рубрики

Вызов сериализатора по умолчанию из пользовательского сериализатора в Джексоне

Узнайте, как вызывать сериализаторы Джексона по умолчанию в пользовательском сериализаторе.

Автор оригинала: baeldung.

1. введение

Сериализация нашей полной структуры данных в 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 :

{
    "name": "Root Folder",
    "files": [
        {"id": 1, "name": "File 1"},
        {"id": 2, "name": "File 2"}
    ]
}

Как мы увидим в следующих разделах, есть несколько способов добиться желаемого результата в Джексоне.

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 :

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

Таким образом, Джексон просто справляется с тяжелой работой, сериализуя Список объектов File , и тогда наш вывод будет таким же.

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. Возможная Проблема Рекурсии

В зависимости от варианта использования нам может потребоваться расширить наши сериализованные данные, включив дополнительные сведения для Папки . Это может быть для устаревшей системы или внешнего приложения, которое будет интегрировано, и у нас нет возможности изменить .

Давайте изменим наш сериализатор, чтобы создать поле 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 defaultSerializer;

public FolderJsonSerializer(JsonSerializer 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) serializer);
        }

        return serializer;
    }
}

Теперь нам нужно зарегистрировать наш BeanSerializerModifier в качестве модуля, чтобы он работал:

ObjectMapper mapper = new ObjectMapper();

SimpleModule module = new SimpleModule();
module.setSerializerModifier(new FolderBeanSerializerModifier());

mapper.registerModule(module);

Затем мы используем сериализатор по умолчанию для поля сведения :

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

Наконец, мы можем захотеть удалить поле files из details , поскольку мы уже записываем его в сериализованные данные отдельно.

Таким образом, мы просто игнорируем поле files в нашем классе Folder :

@JsonIgnore
private List 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
    }
}

6. Заключение

В этом уроке мы узнали, как вызывать сериализаторы по умолчанию внутри пользовательского сериализатора в библиотеке Джексона.

Как всегда, все примеры кода, используемые в этом учебнике, доступны на GitHub .