Spring custom (des) serialización de fecha y hora

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:





  1. 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





  2. 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í








All Articles