Spring Boot uses jackson by default to serialize, deserialize json data.
By default, Jackson serializes Date
objects as timestamps. For LocalDateTime
, LocalDate
objects, jackson doesn’t do anything special, it just treats them as basic Java objects.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
public class MainTest {
public static void main(String... args) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> map = new HashMap<>();
map.put("date", new Date());
map.put("localDateTime", LocalDateTime.now());
map.put("localDate", LocalDate.now());
String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(map);
System.out.println(json);
}
}
|
Output.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
{
"date" : 1663680273923,
"localDateTime" : {
"dayOfMonth" : 20,
"dayOfWeek" : "TUESDAY",
"dayOfYear" : 263,
"month" : "SEPTEMBER",
"monthValue" : 9,
"year" : 2022,
"hour" : 21,
"minute" : 24,
"nano" : 992000000,
"second" : 33,
"chronology" : {
"id" : "ISO",
"calendarType" : "iso8601"
}
},
"localDate" : {
"year" : 2022,
"month" : "SEPTEMBER",
"era" : "CE",
"dayOfMonth" : 20,
"dayOfWeek" : "TUESDAY",
"dayOfYear" : 263,
"leapYear" : false,
"monthValue" : 9,
"chronology" : {
"id" : "ISO",
"calendarType" : "iso8601"
}
}
}
|
The above code runs normally in Java8. If you are in a higher version of java, such as java17, running the above code may throw an exception.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.>LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through >reference chain: java.util.HashMap["localDateTime"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1300)
at com.fasterxml.jackson.databind.ser.impl.UnsupportedTypeSerializer.serialize(UnsupportedTypeSerializer.java:35)
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeFields(MapSerializer.java:808)
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeWithoutTypeInfo(MapSerializer.java:764)
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:720)
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:35)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319)
at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1518)
at com.fasterxml.jackson.databind.ObjectWriter._writeValueAndClose(ObjectWriter.java:1219)
at com.fasterxml.jackson.databind.ObjectWriter.writeValueAsString(ObjectWriter.java:1086)
at io.springboot.test.MainTest.main(MainTest.java:22)
|
Date
The formatting of the Date
object can be easily customized through the configuration properties provided by spring boot.
1
2
3
4
5
6
|
spring:
jackson:
# Date format string or a fully-qualified date format class name. For instance, 'yyyy-MM-dd HH:mm:ss'
date-format: "yyyy-MM-dd HH:mm:ss.SSS"
# Locale used for formatting
time-zone: "GMT+8"
|
LocalDateTime & LocalDate
Spring Boot does not provide configuration properties for formatting LocalDateTime
and LocalDate
, but it does provide a Jackson2ObjectMapperBuilderCustomizer
interface to easily customize the formatting of LocalDateTime
and LocalDate
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
import java.time.format.DateTimeFormatter;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
@Configuration
public class JacksonConfiguration {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> {
// formatter
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// deserializers
builder.deserializers(new LocalDateDeserializer(dateFormatter));
builder.deserializers(new LocalDateTimeDeserializer(dateTimeFormatter));
// serializers
builder.serializers(new LocalDateSerializer(dateFormatter));
builder.serializers(new LocalDateTimeSerializer(dateTimeFormatter));
};
}
}
|
Testing
A simple test to verify that the above code and configuration takes effect.
Controller
A simple Controller that reads and parses the client’s request body into a payload
object, which defines the LocalDate
, LocalDateTime
fields. And it responds to the client with the payload
object to verify that the custom formatting is working.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import lombok.Data;
@Data
class Payload {
private LocalDate date;
private LocalDateTime dateTime;
}
@RestController
@RequestMapping("/test")
public class TestController {
@PostMapping
public Object test (@RequestBody Payload payload) {
Map<String, Object> ret = new HashMap<>();
ret.put("payload", payload); // request body
ret.put("now", new Date());
return ret;
}
}
|
Client
Use Postman to launch http requests.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
POST /test HTTP/1.1
Content-Type: application/json
User-Agent: PostmanRuntime/7.29.2
Accept: */*
Postman-Token: 1b1cbcad-475e-49a9-ad5d-5a8163bd7b05
Host: localhost
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 70
{
"date": "2022-09-20",
"dateTime": "2022-09-20 21:02:00"
}
HTTP/1.1 200 OK
Content-Encoding: gzip
Connection: keep-alive
Server: PHP/7.3.1
X-Request-Id: 6038513c-fa65-49bf-8e6c-44f5db749832
Transfer-Encoding: chunked
Content-Type: application/json
Date: Tue, 20 Sep 2022 14:18:09 GMT
{"payload":{"date":"2022-09-20","dateTime":"2022-09-20 21:02:00"},"now":"2022-09-20 22:18:09.318"}
|
With the request and response logs, you can see that everything is OK.
For more information about the available configurations of jackson in spring boot, you can refer to the official documentation。