Spring Boot 中默认使用的 JSON 框架是 jackson,它负责把 JSON 请求体反序列化为 Java 对象,并把响应给客户端的 Java 对象序列化为 JSON 字符串。
本文将会详细介绍如何在 Spring Boot 应用中自定义 jackson 对 Date
、LocalDateTime
、LocalDate
等日期对象的序列化、反序列化格式。
Jackson 对 Date/LocalDateTime/LocalDate 的默认处理方式 {#jackson-对-datelocaldatetimelocaldate-的默认处理方式}
为了处理 java.time
类型的日期类,你还需要在项目中添加 com.fasterxml.jackson.datatype:jackson-datatype-jsr310
模块。
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
默认情况下,Jackson 将 Date
对象序列化为时间戳。对于 LocalDateTime
和 LocalDate
对象,jackson 会序列化为一个 long[]
,其中,从第一个元素开始分别表示日期的:年、月、日、时、分、秒、毫秒。
示例:
package cn.springdoc.test;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
public class JacksonTest {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
// 注册 JavaTime 模块
objectMapper.registerModule(new JavaTimeModule());
Map<String, Object> map = new HashMap<>();
// Date 类型
map.put("date", new Date());
// LocalDateTime 类型
map.put("localDateTime", LocalDateTime.now());
// LocalDate 类型
map.put("localDate", LocalDate.now());
String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(map);
System.out.println(json);
}
}
输出如下:
{
"date" : 1693533643706,
"localDateTime" : [ 2023, 9, 1, 10, 0, 43, 714833900 ],
"localDate" : [ 2023, 9, 1 ]
}
Date 的格式化 {#date-的格式化}
通过 Spring Boot 提供的配置属性,可以轻松自定义 Date
对象的序列化、反序列化格式。
spring:
jackson:
# Date 对象的格式化方式
date-format: "yyyy-MM-dd HH:mm:ss"
# 格式化用的时区
time-zone: "GMT+8"
LocalDateTime 和 LocalDate 的格式化 {#localdatetime-和-localdate-的格式化}
Spring Boot 未提供与 LocalDateTime
和 LocalDate
相关的格式化配置属性,但提供了一个 Jackson2ObjectMapperBuilderCustomizer
接口,使得我们可以通过 @Configuration
的方式轻松自定义 LocalDateTime
和 LocalDate
的序列化、反序列化格式。
package cn.springdoc.demo.config;
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 -> {
// LocalDate 和 LocalDateTime 的格式化方式
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 设置 LocalDate 和 LocalDateTime 反序列化器
builder.deserializers(new LocalDateDeserializer(dateFormatter));
builder.deserializers(new LocalDateTimeDeserializer(dateTimeFormatter));
// 设置 LocalDate 和 LocalDateTime 序列化器
builder.serializers(new LocalDateSerializer(dateFormatter));
builder.serializers(new LocalDateTimeSerializer(dateTimeFormatter));
};
}
}
测试 {#测试}
创建一个简单的 controller 和 model 类。该 model 类中定义了三种日期类型的字段,并且使用这个 model 类作为 controller 唯一 handler 方法的请求体(反序列化)和响应体(序列化)。
package cn.springdoc.demo.controller;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
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;
// Payload 请求体/响应体对象
class Payload {
// Date 类型字段
private Date date;
// LocalDate 类型字段
private LocalDate localDate;
// LocalDateTime 类型字段
private LocalDateTime localDateTime;
// 省略 get/set 方法 ...
}
@RestController
@RequestMapping("/test")
public class TestController {
@PostMapping
public Object test (@RequestBody Payload payload) {
// 直接把请求体响应给客户端。
return payload;
}
}
使用 Postman 发起请求:
POST /test HTTP/1.1
Origin: http://localhost:8080/
Access-Control-Request-Headers: Foo
Access-Control-Request-Method: GET
Content-Type: application/json
User-Agent: PostmanRuntime/7.29.2
Accept: */*
Postman-Token: 8c5bcaff-a2f5-4231-be32-1251c0db1794
Host: localhost:8080
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 116
{
"date": "2023-09-01 10:20:28",
"localDate": "2023-09-01",
"localDateTime": "2023-09-01 10:20:28"
}
HTTP/1.1 200 OK
Connection: keep-alive
Transfer-Encoding: chunked
Content-Type: application/json
Date: Fri, 01 Sep 2023 02:22:19 GMT
{"date":"2023-09-01 10:20:28","localDate":"2023-09-01","localDateTime":"2023-09-01 10:20:28"}
我们往测试端点 POST
了一个 JSON 字符,其中包含了3个日期字段,这些日期格式正是使用了我们在上述中定义的格式。
{
"date": "2023-09-01 10:20:28",
"localDate": "2023-09-01",
"localDateTime": "2023-09-01 10:20:28"
}
你可以看到请求体和响应体中的日期数据完全一致,Spring Boot 确实是按照我们指定的日期格式进行了序列化和反序列化。