Spring (dé) sérialisation personnalisée de la date et de l'heure

Imaginez une situation assez courante: votre application interagit avec des clients qui se trouvent dans des fuseaux horaires différents. Vous devez souvent travailler avec des dates, et pour que le système fonctionne correctement, elles sont envoyées avec le fuseau horaire de l'expéditeur. Pour ce faire, vous avez besoin:





  1. Lorsqu'une demande est reçue, mettez la date à l'heure du serveur et travaillez avec elle, et enregistrez-la également dans la base de données sous ce formulaire





  2. En réponse, renvoyez la date et l'heure indiquant le fuseau horaire du serveur





Pour ce faire, Spring fournit un mécanisme pratique pour écrire une sérialisation et une désérialisation personnalisées. Son principal avantage est la possibilité de déplacer les conversions de date (et d'autres types de données) dans une classe de configuration distincte et de ne pas appeler les méthodes de conversion à chaque fois dans le code source.





Désérialisation

Pour que Spring comprenne que c'est notre classe qui doit être utilisée pour la (dé) sérialisation, elle doit être marquée avec l'annotation @JsonComponent







Eh bien, pour garder le code aussi concis que possible, j'utiliserai une classe statique interne, qui doit être héritée JsonDeserializer



et paramétrée avec le type de données dont nous avons besoin. Puisqu'il JsonDeserializer



 s'agit d'une classe abstraite, nous devons remplacer sa méthode abstraitedeserialize()







@JsonComponent
public class CustomDateSerializer {  
  
    public static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
    
        @Override
        public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) {
            return null;
        }
    }
}
      
      



À partir des paramètres de la méthode, nous obtenons la chaîne passée par le client, la vérifions pour null et en obtenons un objet de classe 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







Puisque nous travaillons avec une chaîne, il ne nous sera pas difficile de modifier un peu le code pour que nous obtenions toujours la date dans le même format à la sortie. Déclarons deux constantes - l'une d'elles sera égale à l'ID par défaut UTC + 00, et la seconde - que nous voulons donner au client, et ajoutons une vérification - si l'heure du serveur est dans le fuseau horaire zéro, alors nous le remplacera Z



par +00:00



. En conséquence, notre sérialiseur ressemblera à ceci





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

Grâce aux mécanismes de ressort intégrés, nous avons pu convertir automatiquement la date et l'heure au format requis, sans aucun appel de méthode explicite dans le code





Le code source complet peut être consulté ici








All Articles