添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
英俊的紫菜  ·  MySQL :: MySQL 8.4 ...·  1 月前    · 
爱搭讪的大蒜  ·  如何在EXCEL ...·  1 月前    · 
善良的莴苣  ·  Java 时间工具包 xk-time ...·  6 天前    · 
奔放的梨子  ·  DoCmd.Hourglass 方法 ...·  7 月前    · 
安静的感冒药  ·  vue elementUI ...·  2 年前    · 
卖萌的红金鱼  ·  前端 - 解决Element UI ...·  2 年前    · 

学习如何在SpringBoot应用中自定义 Date/LocalDateTime/LocalDate 对象的JSON格式化。

Spring Boot默认使用 jackson 来序列化、反序列化json数据。

默认情况下,Jackson将 Date 对象序列化为时间戳。对于 LocalDateTime LocalDate 对象,Jackson没有做任何特别的事情,它只是把它们当作基本的Java对象。

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);

输出如下。

"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"

上述代码在Java8中正常运行。如果你是在更高版本的java中,比如java17,运行上述代码可能会出现异常。

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)

通过spring boot提供的配置属性,可以很容易地定制Date对象的格式化。

spring:
  jackson:
    # 格式化设置
    date-format: "yyyy-MM-dd HH:mm:ss.SSS"
    time-zone: "GMT+8"
LocalDateTime 和 LocalDate

Spring Boot 没有提供用于格式化 LocalDateTimeLocalDate 的配置属性,但它提供了一个 Jackson2ObjectMapperBuilderCustomizer 接口,可以轻松定制 LocalDateTimeLocalDate 的格式化。

并且jackson还给我们预定义了 LocalDateDeserializerLocalDateSerializerLocalDateTimeDeserializerLocalDateTimeSerializer 几个类,使得自定义日期对象的格式化更加简单。

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 -> {
            // 格式
            DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
            DateTimeFormatter dateTimeFormatter =  DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            //反序列化
            builder.deserializers(new LocalDateDeserializer(dateFormatter));
            builder.deserializers(new LocalDateTimeDeserializer(dateTimeFormatter));
            // 序列化
            builder.serializers(new LocalDateSerializer(dateFormatter));
            builder.serializers(new LocalDateTimeSerializer(dateTimeFormatter));

一个简单的测试来验证上述代码和配置是否生效。

Controller

一个简单的Controller,它读取并解析客户端的请求体为 payload 对象中,其中定义了 LocalDateLocalDateTime 字段。并且直接把 payload 对象响应客户端,以验证自定义格式化是否有效。

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); // 客户端请求体
        ret.put("now", new Date());
        return ret;
客户端发起请求

使用 Postman 请求上面的Controller,请求和响应日志如下。

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"}

如你所见,一切OK。

ZonedDateTime

如果你需要格式化 ZonedDateTime 对象,那么就需要自己定义jackson的serializer和deserializer。

跟上面其实大同小异,代码如下。

@Bean
public Jackson2ObjectMapperBuilderCustomizer zonedDateTimeCustomizer() {
	DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
	return builder -> {
		builder.serializers(new StdSerializer<>(ZonedDateTime.class) {
			private static final long serialVersionUID = 7267001379918924374L;
			@Override
			public void serialize(ZonedDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
				gen.writeString(value.format(dateTimeFormatter));
		builder.deserializers(new StdDeserializer<>(ZonedDateTime.class) {
			private static final long serialVersionUID = 154543596456722486L;
			@Override
			public ZonedDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
				return ZonedDateTime.parse(p.getText(), dateTimeFormatter.withZone(ZoneId.systemDefault()));  // 注意这里要指定时区,不然会异常