
В данной заметке я расскажу, как в spring data rest сериализовать в json сложный объект в виде строки. Советы справедливы для spring-data-rest версии 2.5.4. Пусть есть класс Topic с коллекцией сущностей Record, а у класса Record есть поле:
java.time.LocalDate releaseDate; |
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; |
@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"
}
} |
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;
} |
@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.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.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);
}
} |
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);
}
}