> 文章列表 > SpringBoot JSON全局日期格式转换器

SpringBoot JSON全局日期格式转换器

SpringBoot JSON全局日期格式转换器

参考资料

  1. SpringBoot日期格式转换,SpringBoot配置全局日期格式转换器
  2. 在Spring Boot中定制Jackson ObjectMapper详解

目录

  • 需求
  • 一. 前期准备
    • 1.1 日期正则注解
    • 1.2 日期格式定数
    • 1.3 日期转换工具类
  • 二. 方式1-继承DateDeserializer类,重写_parseDate方法
  • 三. 方式2-继承StdDateFormat类,重写方法
    • 3.1 MappingJackson2HttpMessageConverter方式
    • 3.2 ObjectMapper方式
    • 3.3 Jackson2ObjectMapperBuilder方式
  • 四. 效果
  • 五. 总结

需求

前台有日期字符串的数据,提交到后台。后台实体类使用Date属性接收。
日期字符串有多种格式,需要用一个转换器将合法的日期字符串格式转换为Date类型。


一. 前期准备

1.1 日期正则注解

  • 用来标记日期字符串所对应的正则表达式
import java.lang.annotation.*;@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface DatePattern {String value();
}

1.2 日期格式定数

  • 指定所有支持的日期格式
public final class DateFormatPart {@DatePattern("^\\\\d{4}$")public static final String YYYY = "yyyy";@DatePattern("^\\\\d{4}\\\\d{2}$")public static final String YYYYMM = "yyyyMM";@DatePattern("^\\\\d{4}/\\\\d{2}$")public static final String YYYYMM_SLASH = "yyyy/MM";@DatePattern("^\\\\d{4}\\\\d{1,2}\\\\d{1,2}$")public static final String YYYYMMDD = "yyyyMMdd";@DatePattern("^\\\\d{4}/\\\\d{2}/\\\\d{2}$")public static final String YYYYMMDD_SLASH = "yyyy/MM/dd";@DatePattern("[0-9]+\\\\u5e74[0-9]+\\\\u6708[0-9]+\\\\u65e5$")public static final String YYYYMMDD_JP = "yyyy年MM月dd日";@DatePattern("^\\\\d{4}\\\\d{1,2}\\\\d{1,2} \\\\d{1,2}:\\\\d{1,2}:\\\\d{1,2}$")public static final String YYYYMMDD_HHMMSS = "yyyyMMdd HH:mm:ss";@DatePattern("^\\\\d{4}/\\\\d{2}/\\\\d{2} \\\\d{1,2}:\\\\d{1,2}:\\\\d{1,2}$")public static final String YYYYMMDD_HHMMSS_SLASH = "yyyy/MM/dd HH:mm:ss";
}

1.3 日期转换工具类

  • 从日期格式定数类中获取所有的属性值和该属性上所标记的正则注解,通过反射来映射为map。
  • 如果有需要增删的日期格式的话,只需要修改日期格式定数即可,便于维护。
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;import java.lang.reflect.Field;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;public final class DateUtil {// 日期格式 <==> 正则的map,使用LinkedHashMap可以避免按照顺序匹配正则表达式private static final Map<String, String> datePatternMap = new LinkedHashMap<>();// 日期格式Listprivate static final List<String> dateFormatList = new ArrayList<>();// 使用静态代码块,可以保证只初始化一次static {Class<DateFormatPart> dateFormatClass = DateFormatPart.class;// 获取所有的属性Field[] fields = dateFormatClass.getFields();for (Field field : fields) {// 获取属性上的注解DatePattern annotation = field.getAnnotation(DatePattern.class);if (ObjectUtils.isEmpty(annotation)) {continue;}//  强制让属性可以访问ReflectionUtils.makeAccessible(field);// 日期格式化字符串String dateFormatStr = "";try {// 获取当前属性所对应的属性值dateFormatStr = (String)field.get(dateFormatClass);} catch (IllegalAccessException e) {e.printStackTrace();}dateFormatList.add(dateFormatStr);// 将该属性的属性值和属性上的正则表达式放到map中datePatternMap.put(dateFormatStr, annotation.value());}}// 用所有可能支持的格式将日期字符串转换为Date格式public static Date formatDateStrToDateAllFormat(String dateStr) {if (ObjectUtils.isEmpty(dateStr)) {return null;}try {for (Map.Entry<String, String> mapEntry : datePatternMap.entrySet()) {// 如果当前日期字符串不符合当前正则的话if (!dateStr.matches(mapEntry.getValue())) {continue;}return DateUtil.formatStringToDate(dateStr, mapEntry.getKey());}} catch (ParseException e) {return null;}return null;}// 通过指定的格式将日期字符串转换为Date类型public static Date formatStringToDate(String dateStr, String format) throws ParseException {if (ObjectUtils.isEmpty(dateStr) || !dateFormatList.contains(format)) {return null;}SimpleDateFormat time = new SimpleDateFormat(format);return time.parse(dateStr);}
}

二. 方式1-继承DateDeserializer类,重写_parseDate方法

  • 该方式的要点是通过继承DateDeserializer类,然后重写_parseDate方法实现转换功能
  • 自定义的GlobalDateDeserializer类需要添加到自定义的SimpleModule模块中,然后将模块添加到ObjectMapper中,通过jackson来实现转换。
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.json.PackageVersion;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.deser.std.DateDeserializers.DateDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;@Configuration
public class CustomConfig {/ 自定义的全局的日期转换解析类,需要继承jackson包中的DateDeserializer* 因为此类不会在别的地方使用了,因此直接使用内部类聚合到自定义的配置文件中*/private static final class GlobalDateDeserializer extends DateDeserializer {@Overrideprotected Date _parseDate(JsonParser jp, DeserializationContext context) throws IOException {return DateUtil.formatDateStrToDateAllFormat(jp.getText());}}@Bean("objectMapper")@Primary@ConditionalOnMissingBeanpublic ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {// 创建jackson对象ObjectMapper jackson = builder.createXmlMapper(false).build();/ DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES* 在进行序列化或者反序列化的时候,* JSON字符串中有一个字段,但是我们的对象没有这个字段的时候,该怎么处理* 设置为true的时候,会抛出异常* 设置为false,忽略异常继续处理*/jackson.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);// 禁用默认的「yyyy-MM-dd'T'HH:mm:ss.SSS」UTC类型jackson.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);// 设置默认的日期格式DateFormat dateFormat = new SimpleDateFormat(DateFormatPart.YYYYMMDD_HHMMSS_SLASH);jackson.setDateFormat(dateFormat);// 创建一个模块,指定该模块是用来解析 Date.class 类型数据的SimpleModule newModule = new SimpleModule("GlobalDateDeserializer", PackageVersion.VERSION);// 将我们创建的全局日期转换类添加到模块中,指定转换Date类型newModule.addDeserializer(Date.class, new CustomConfig.GlobalDateDeserializer());// 将该模块添加到jackson中jackson.registerModule(newModule);return jackson;}
}

三. 方式2-继承StdDateFormat类,重写方法

import java.text.FieldPosition;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;import com.fasterxml.jackson.databind.util.StdDateFormat;public class GlobalJsonDateConvert extends StdDateFormat {// 静态初始化final,共享public static final GlobalJsonDateConvert instance = new GlobalJsonDateConvert();// 日期字符串解析为日期@Overridepublic Date parse(String dateStr, ParsePosition pos) {return getDate(dateStr);}@Overridepublic Date parse(String dateStr) {return getDate(dateStr);}// 使用自定义的日期转换工具类将所有可能支持的日期字符串转换为Date格式private Date getDate(String dateStr) {return DateUtil.formatDateStrToDateAllFormat(dateStr);}@Overridepublic StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition){SimpleDateFormat sdf = new SimpleDateFormat(DateFormatPart.YYYYMMDD_HHMMSS_SLASH);return sdf.format(date, toAppendTo, fieldPosition);}@Overridepublic GlobalJsonDateConvert clone() {super.clone();return new GlobalJsonDateConvert();}
}

3.1 MappingJackson2HttpMessageConverter方式

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;@Configuration
public class CustomConfig {// JSON格式 全局日期转换器配置@Beanpublic MappingJackson2HttpMessageConverter createMappingJackson2HttpMessageConverter() {/ 通过MappingJackson2HttpMessageConverter得到的ObjectMapper,* 已经默认把 DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES 给关闭了*/MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter();jacksonConverter.getObjectMapper().setDateFormat(GlobalJsonDateConvert.instance);return jacksonConverter;}
}

3.2 ObjectMapper方式

  • 通过Jackson2ObjectMapperBuilder创建ObjectMapper对象,然后将我们定义的转换器GlobalJsonDateConvert放到ObjectMapper对象中
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;@Configuration
public class CustomConfig {@Bean("objectMapper")@Primary@ConditionalOnMissingBeanpublic ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {ObjectMapper objectMapper = builder.build();objectMapper.setDateFormat(GlobalJsonDateConvert.instance);return objectMapper;}
}

3.3 Jackson2ObjectMapperBuilder方式

  • 将我们定义的转换器GlobalJsonDateConvert放到Jackson2ObjectMapperBuilder对象中
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;@Configuration
public class CustomConfig {@Beanpublic Jackson2ObjectMapperBuilder objectMapper() {Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder = new Jackson2ObjectMapperBuilder();jackson2ObjectMapperBuilder.dateFormat(GlobalJsonDateConvert.instance);return jackson2ObjectMapperBuilder;}
}

四. 效果

⏹前台JS

// 向后台传输的json数据
const jsonData = {// 👉待处理的日期字符串数据birthday: '2027年12月12日',nameAA: 'jiafeitian',hobby: '吃饭'
};$.ajax({url: url,type: 'POST',// 对象转换为json字符串data: JSON.stringify(jsonData),contentType: "application/json;charset=utf-8",success: function (data, status, xhr) {console.log(data);}
});

⏹后台Form

import lombok.Data;
import java.util.Date;@Data
public class Test15Form {private String name;private String hobby;private String address;// 用来接收的Date类型的数据private Date birthday;
}

👇可以看到前台提交的日期字符串被转换为Date格式了

SpringBoot JSON全局日期格式转换器


五. 总结

方式一中的1种方法和方式二中的3种方法,共计4中方法都可以实现全局日期格式转换器。
要点就是自定义日期转换的工具类用来处理各种可能的日期格式,并且将此工具类放到Jackson中的ObjectMapper对象中,上述4中方法的本质都是如此。