Imagine una situación bastante común: su aplicación interactúa con clientes que se encuentran en diferentes zonas horarias. A menudo tienes que trabajar con fechas y, para que el sistema funcione correctamente, se envían con la zona horaria del remitente. Al hacerlo, necesita:
Cuando se reciba una solicitud, lleve la fecha a la hora del servidor y trabaje con ella, y también guárdela en la base de datos en este formulario
En respuesta, devuelva la fecha y la hora indicando la zona horaria del servidor.
Para lograr esto, Spring proporciona un mecanismo conveniente para escribir serialización y deserialización personalizadas. Su principal ventaja es la capacidad de mover conversiones de fecha (y otros tipos de datos) a una clase de configuración separada y no llamar a los métodos de conversión cada vez en el código fuente.
Deserialización
Para que Spring comprenda que es nuestra clase la que debe usarse para (des) serialización, debe estar marcada con la anotación @JsonComponent
Bueno, para mantener el código lo más conciso posible, usaré una clase estática interna, que debe ser heredada JsonDeserializer
y parametrizada con el tipo de datos que necesitamos. Dado que JsonDeserializer
es una clase abstracta, necesitamos anular su método abstractodeserialize()
@JsonComponent
public class CustomDateSerializer {
public static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
@Override
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) {
return null;
}
}
}
De los parámetros del método, obtenemos la cadena pasada por el cliente, verificamos que no sea nula y obtenemos un objeto de clase de ella ZonedDateTime
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) {
String date = jsonParser.getText();
if (date.isEmpty() || isNull(date) {
return null;
}
ZonedDateTime userDateTime = ZonedDateTime.parse(date);
}
, userDateTime
withZoneSameInstant()
. LocalDateTime
, , , . , .
public static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
@Override
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
String date = jsonParser.getText();
if (date.isEmpty()) {
return null;
}
try {
ZonedDateTime userDateTime = ZonedDateTime.parse(date);
ZonedDateTime serverTime = userDateTime.withZoneSameInstant(ZoneId.systemDefault());
return serverTime.toLocalDateTime();
} catch (DateTimeParseException e) {
try {
return LocalDateTime.parse(date);
} catch (DateTimeParseException ex) {
throw new IllegalArgumentException("Error while parsing date", ex);
}
}
}
}
, UTC+03. , 2021-01-21T22:00:00+07:00
,
public class Subscription {
private LocalDateTime startDate;
// standart getters and setters
}
@RestController
public class TestController {
@PostMapping
public void process(@RequestBody Subscription subscription) {
// startDate subscription 2021-01-21T18:00
}
}
. JsonSerializer
, serialize()
null, .
public static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
@Override
public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (isNull(localDateTime)) {
return;
}
OffsetDateTime timeUtc = localDateTime.atOffset(ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now()));
jsonGenerator.writeString(timeUtc.toString());
}
}
? ? . , , , UTC+00. , id . ZoneOffset
, UTC+03, : 2021-02-21T18:00+03:00.
UTC+00, 2021-02-21T18:00Z
Dado que estamos trabajando con una cadena, no será difícil para nosotros cambiar un poco el código para que siempre obtengamos la fecha en el mismo formato en la salida. Declaremos dos constantes, una de ellas será igual a la identificación predeterminada UTC + 00, y la segunda, que queremos darle al cliente, y agreguemos un cheque, si la hora del servidor está en la zona horaria cero, entonces lo reemplazará Z
con +00:00
. Como resultado, nuestro serializador se verá así
public static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
private static final String UTC_0_OFFSET_ID = "Z";
private static final String UTC_0_TIMEZONE = "+00:00";
@Override
public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (!isNull(localDateTime)) {
String date;
OffsetDateTime timeUtc = localDateTime.atOffset(ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now()));
if (UTC_0_OFFSET_ID.equals(timeUtc.getOffset().getId())) {
date = timeUtc.toString().replace(UTC_0_OFFSET_ID, UTC_0_TIMEZONE);
} else {
date = timeUtc.toString();
}
jsonGenerator.writeString(date);
}
}
}
Total
Gracias a los mecanismos de resorte incorporados, pudimos convertir automáticamente la fecha y la hora en el formato requerido, sin ninguna llamada explícita al método en el código.
El código fuente completo se puede ver aquí