使用@JsonFormat的一个坑及解决

使用@JsonFormat注解可以自定义将Java对象转换为JSON字符串时的日期格式,但是在使用过程中也存在一个坑点。具体攻略如下:

使用@JsonFormat注解可以自定义将Java对象转换为JSON字符串时的日期格式,但是在使用过程中也存在一个坑点。具体攻略如下:

1.问题描述

我们在使用@JsonFormat注解时,想要将日期格式化为类似"yyyy-MM-dd HH:mm:ss.SSS"的字符串格式,于是我们在实体类上添加该注解:

public class User {
    private Long id;
    private String name;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS", timezone = "Asia/Shanghai")
    private Date createTime;
    //getters/setters...
}

在将该实体类转换为JSON字符串时,我们期望得到的日期格式应该是"2021-08-31 15:30:40.123"这样的结果。但是实际上转换后却是"2021-09-01T07:30:40.123+0000"这样的结果,时间格式和时区都并不是我们设置的内容。

2.问题分析

JsonFormat注解中的"timezone"属性,其实是将java中的Date对象按照指定的timezone转为标准时间字符串,然后再转换成JSON字符串。因此,问题就在于@JsonFormat注解没有将我们设置的时间格式直接应用到Date对象上,因为各个时区的时间格式可能是不同的。

3.解决方案

要解决这个问题,我们需要在转换JSON字符串时,指定jackson的序列化配置。具体来说,就是通过自定义ObjectMapper,设置其默认的输出时间格式和时区。以下是两种实现方式:

方式一:编写ObjectMapperProvider

编写ObjectMapperProvider,设置默认的输出时间格式和时区:

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.TimeZone;

public class CustomObjectMapperProvider {

    private static final Logger log = LoggerFactory.getLogger(CustomObjectMapperProvider.class);

    public ObjectMapper createObjectMapper() {
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        dateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));

        ObjectMapper objectMapper = new Jackson2ObjectMapperBuilder().build();

        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        objectMapper.setDateFormat(dateFormat);

        // Register JavaTimeModule
        objectMapper.registerModule(new JavaTimeModule());
        // Fix Jackson2DateFormat issue (https://github.com/FasterXML/jackson-databind/issues/1072)
        objectMapper.findAndRegisterModules();

        return objectMapper;
    }
}

在上面的例子中,我们使用了SimpleDateFormat指定了默认输出的时间格式,同时也设置了时区。我们通过Jackson2ObjectMapperBuilder来构建ObjectMapper对象,然后通过ObjectMapper的方法设置它的序列化配置。最后,在ObjectMapper中注册JavaTimeModule(解决LocalDateTime转换的问题)。

方式二:编写自定义序列化器

除了通过ObjectMapper的方式来解决这个问题之外,我们还可以编写自定义序列化器,将Java中的Data对象按照我们预期的方式进行序列化。以下是示例代码:

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.springframework.boot.jackson.JsonComponent;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

@JsonComponent
public class JsonDateSerializer extends JsonSerializer<Date> {

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    static {
        dateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
    }

    @Override
    public void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        gen.writeString(dateFormat.format(value));
    }
}

在上面的代码中,我们使用@JsonComponent注解将自定义的序列化器注册到Spring Boot框架中。同时,我们也在该序列化器中使用了SimpleDateFormat指定了默认输出的时间格式,同时也设置了时区。注意,这个方法里必须手动写代码将日期转为字符串进行输出。

4.总结

当使用@JsonFormat时,无法直接实现对Java对象的日期格式化。应该使用自定义ObjectMapper或者自定义序列化器的方式,对日期格式和时区进行配置,才能正确地将Java对象转换为JSON字符串。

本文标题为:使用@JsonFormat的一个坑及解决

基础教程推荐