Настройка json-сериализации в spring data rest (hateoas) с помощью BeanPostProcessor

В данной заметке я расскажу, как в spring data rest сериализовать в json сложный объект в виде строки. Советы справедливы для spring-data-rest версии 2.5.4. Пусть есть класс Topic с коллекцией сущностей Record, а у класса Record есть поле:
java.time.LocalDate releaseDate;
При обслуживании HTTP запросов к единичному объекту (например, http://localhost:8080/server/records/5 ) сериализации можно добиться аннотациями:
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    @Column(name = "release_date")
    LocalDate releaseDate;
При получении же списка ( http://localhost:8080/server/records или http://localhost:8080/server/topics/1/recordList/ ) вы получите вместо строки вида "2016-12-01" полноценный, но совершенно ненужный массив свойств:
releaseDate": {
 
    "year": 2016,
    "month": "DECEMBER",
    "dayOfMonth": 1,
    "monthValue": 12,
    "dayOfWeek": "THURSDAY",
    "era": "CE",
    "dayOfYear": 336,
    "leapYear": true,
    "chronology": {
        "id": "ISO",
        "calendarType": "iso8601"
    }
}
Я испробовал много всего, к примеру навешивал аннотации на getter:
@JsonSerialize(using = LocalDateSerializer.class)
@JsonFormat("yyyy-MM-dd")
public LocalDate getReleaseDate() {
    return releaseDate;
}
Но по всему выходило, что аннотации спрингом игнорируются. Решение нашел тут: дельная ссылка Можно изменить способ сериализации для типа во всех классах сразу, при этом на тип больше не надо вешать аннотации. В пакете, который прошаривается аннотацией ComponentScan нужно создать класс:
package ru.outofrange.config;
 
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import ru.outofrange.model.util.JsonDateDeserializer;
import ru.outofrange.model.util.JsonDateSerializer;
 
import java.time.LocalDate;
 
@Component
public class ObjectMapperCustomizer implements BeanPostProcessor {
 
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
 
        if (!(bean instanceof ObjectMapper)) {
            return bean;
        }
 
        ObjectMapper mapper = (ObjectMapper) bean;
        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
 
        registerSerializerDserializer(mapper);
        return mapper;
    }
 
    private void registerSerializerDserializer(ObjectMapper mapper) {
        SimpleModule module = new SimpleModule();
 
        module.addSerializer(LocalDate.class, new JsonDateSerializer());
        module.addDeserializer(LocalDate.class, new JsonDateDeserializer());
 
        mapper.registerModule(module);
    }
 
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}
Как можно видеть по приведенной ссылке, таким образом можно настроить сериализацию любого количества кастомных типов. На всякий случай код еще двух вспомогательных классов-аннотаций:
package ru.outofrange.model.util;
 
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.node.TextNode;
 
import java.io.IOException;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
 
public class JsonDateDeserializer extends JsonDeserializer <LocalDate> {
 
    @Override
    public LocalDate deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
        ObjectCodec oc = jp.getCodec();
        TextNode node = oc.readTree(jp);
        String dateString = node.textValue();
 
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        LocalDate date = LocalDate.parse(dateString, formatter);
        return date;
    }
}
package ru.outofrange.model.util;
 
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
 
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
 
public class JsonDateSerializer extends JsonSerializer <LocalDate> {
 
    private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
 
    @Override
    public void serialize(LocalDate date, JsonGenerator generator, SerializerProvider provider) throws IOException {
        String dateString = date.format(formatter);
        generator.writeString(dateString);
    }
}
You can leave a response, or trackback from your own site.

Leave a Reply