Imagine a fairly common situation: your application interacts with clients that are in different time zones. You often have to work with dates, and for the system to work correctly, they are sent with the time zone of the sender. In doing so, you need:
When a request is received, bring the date to server time and work with it, and also save it to the database in this form
In response, return the date and time indicating the server time zone
To accomplish this, Spring provides a convenient mechanism for writing custom serialization and deserialization. Its main advantage is the ability to move date conversions (and other data types) into a separate configuration class and not call conversion methods every time in the source code.
Deserialization
In order for Spring to understand that it is our class that needs to be used for (de) serialization, it must be marked with the annotation @JsonComponent
Well, to keep the code as concise as possible, I will use an internal static class, which must be inherited from JsonDeserializer
and parameterized with the data type we need. Since it JsonDeserializer
is an abstract class, we need to override its abstract methoddeserialize()
@JsonComponent
public class CustomDateSerializer {
public static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
@Override
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) {
return null;
}
}
}
From the method parameters, we get the string passed by the client, check it for null and get a class object from it 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
Since we are working with a string, it will not be difficult for us to slightly change the code so that we always get the date in the same format at the output. Let's declare two constants - one of them will be equal to the default id UTC + 00, and the second - which we want to give to the client, and add a check - if the server time is in the zero time zone, then we will replace it Z
with +00:00
. As a result, our serializer will look like this
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
Thanks to the built-in spring mechanisms, we were able to automatically convert the date and time in the required format, without any explicit method calls in the code
The full source code can be viewed here