解决全局的jackson日期转换和解析
因为项目有些特殊,需要解析各种格式的日期类型转换,后来发现jackson在日期转换上没法满足需要。
遂重写了com.fasterxml.jackson.databind.util.StdDateFormat
和com.fasterxml.jackson.databind.DeserializationContext。
我的版本是jackson-databind-2.8.10
起初因为需要“yyyy-MM-dd HH:mm:ss”的日期格式直接显示,直接使用了@JsonFormat注解,设置“yyyy-MM-dd HH:mm:ss”,时区加八个小时。
但是被注解后,不管是解析还是json串,都一律是这个格式。只要不是这个格式那就是六亲不认!比如别人的api中接收我的格式时报错“Can not parse date “xx”: not compatible with any of standard forms (xxxx,xxxx)”,我的格式接收别人的api报错 “Failed to parse Date value ‘xx’: yyyy-MM-dd HH:mm:ss”。
于是我尝试在objectMapper中设置全局的DateFormat:
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/statics/**").addResourceLocations("classpath:/statics/");
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = jackson2HttpMessageConverter.getObjectMapper();
//json中多余的参数不报错,不想要可以改掉
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//设置全局的时间转化
SimpleDateFormat smt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
objectMapper.setDateFormat(smt);
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));//解决时区差8小时问题
//设置中文编码格式
List<MediaType> list = new ArrayList<MediaType>();
list.add(MediaType.APPLICATION_JSON_UTF8);
jackson2HttpMessageConverter.setSupportedMediaTypes(list);
//生成json时,将所有Long转换成String
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
jackson2HttpMessageConverter.setObjectMapper(objectMapper);
converters.add(0, jackson2HttpMessageConverter);
}
}
没错,只有三句话:
- SimpleDateFormat smt = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
- objectMapper.setDateFormat(smt);
- objectMapper.setTimeZone(TimeZone.getTimeZone(“GMT+8”));
剩下的全是废话。
这样做只能起到微弱的卵用,仅仅只是让每一个date都带有JsonFormat的效果而已,于解析而言,没有任何益处。
后经过我的不断试验,发现如果我不加任何Format的话,默认可以解析yyyy-MM-dd’T’HH:mm:ss.SSSZ、时间戳、EEE, dd MMM yyyy HH:mm:ss zzz等等一系列惨不忍睹的日期格式,经过我的不断寻找终于找到了,解析的代码所在。就是这个STDDateformat。这里我一共写了几项:
/**
* TODO 自己加入,想加多少加多少
*/
protected final static String DATE_FORMAT_STR_FIRST= "yyyy-MM-dd HH:mm:ss";
protected final static String DATE_FORMAT_STR_SECOND= "EEE MMM dd hh:mm:ss z yyyy";`
/**
* For error messages we'll also need a list of all formats.
*/
protected final static String[] ALL_FORMATS = new String[] {
DATE_FORMAT_STR_ISO8601,
DATE_FORMAT_STR_ISO8601_Z,
DATE_FORMAT_STR_ISO8601_NO_TZ,
DATE_FORMAT_STR_RFC1123,
DATE_FORMAT_STR_PLAIN,
DATE_FORMAT_STR_FIRST,//把刚写的两个转换格式加入
DATE_FORMAT_STR_SECOND
};
protected final static DateFormat DATE_FORMAT_FIRST;//也是同理,照葫芦画瓢
protected final static DateFormat DATE_FORMAT_SECOND;
DATE_FORMAT_FIRST = new SimpleDateFormat(DATE_FORMAT_STR_FIRST, DEFAULT_LOCALE);
DATE_FORMAT_FIRST.setTimeZone(DEFAULT_TIMEZONE);
DATE_FORMAT_SECOND = new SimpleDateFormat(DATE_FORMAT_STR_SECOND, DEFAULT_LOCALE);
DATE_FORMAT_SECOND.setTimeZone(DEFAULT_TIMEZONE);
@Override
public Date parse(String dateStr) throws ParseException
{
dateStr = dateStr.trim();
ParsePosition pos = new ParsePosition(0);
Date dt;
if (looksLikeISO8601(dateStr)) { // also includes "plain"
dt = parseAsISO8601(dateStr, pos, true);
} else {
// Also consider "stringified" simple time stamp
int i = dateStr.length();
while (--i >= 0) {
char ch = dateStr.charAt(i);
if (ch < '0' || ch > '9') {
// 07-Aug-2013, tatu: And [databind#267] points out that negative numbers should also work
if (i > 0 || ch != '-') {
break;
}
}
}
if ((i < 0)
// let's just assume negative numbers are fine (can't be RFC-1123 anyway); check length for positive
&& (dateStr.charAt(0) == '-' || NumberInput.inLongRange(dateStr, false))) {
dt = new Date(Long.parseLong(dateStr));
} else {
// Otherwise, fall back to using RFC 1123
dt = parseAsRFC1123(dateStr, pos);
}
}
if (dt != null) {
return dt;
}
/////////////////////////////////
//这里加入解析是非常重要的一步,这个类有两个parse()方法,写在一个参数的那个parse()方法里面,
//parseDate()建议大家直接引入别人的jar包里封装好的方法,我这里自己写是因为EEE MMM dd hh:mm:ss z yyyy
//的格式必须要设置正确的locale才可以解析
String [] s={DATE_FORMAT_STR_SECOND,DATE_FORMAT_STR_FIRST};
dt = parseDate(dateStr, s,_locale);
if (dt != null) {
return dt;
}
/////////////////////////////////
StringBuilder sb = new StringBuilder();
for (String f : ALL_FORMATS) {
if (sb.length() > 0) {
sb.append("\", \"");
} else {
sb.append('"');
}
sb.append(f);
}
sb.append('"');
throw new ParseException
(String.format("Can not parse date \"%s\": not compatible with any of standard forms (%s)",
dateStr, sb.toString()), pos.getErrorIndex());
}
//虽然不是必要的,但还是将这个方法贴出来
private static Date parseDate(String str, String[] parsePatterns, Locale _locale)
throws ParseException
{
if ((str == null) || (parsePatterns == null)) {
throw new IllegalArgumentException("Date and Patterns must not be null");
}
SimpleDateFormat parser = null;
ParsePosition pos = new ParsePosition(0);
for (int i = 0; i < parsePatterns.length; i++) {
if (i == 0)
parser = new SimpleDateFormat(parsePatterns[0],_locale);
else {
parser.applyPattern(parsePatterns[i]);
}
pos.setIndex(0);
Date date = parser.parse(str, pos);
if ((date != null) && (pos.getIndex() == str.length())) {
return date;
}
}
throw new ParseException("Unable to parse the date: " + str, -1);
}
这个时候发现不对劲
加入全局格式转换后
Failed to parse Date value ‘xx’: yyyy-MM-dd HH:mm:ss
明显和
throw new ParseException (String.format(“Can not parse date \”%s\”: not compatible with any of standard forms (%s)”,
dateStr, sb.toString()), pos.getErrorIndex());
报的错不一样!,
不加类型转换,报下面这个。加了就调皮。
鉴于我人傻头铁时间多,接着找。找到了DeserializationContext。这个类
他的报错是
throw new IllegalArgumentException(String.format(
"Failed to parse Date value '%s': %s", dateStr, e.getMessage()));
这么看来应该是他没跑儿了
//修改parseDate
public Date parseDate(String dateStr) throws IllegalArgumentException
{
try {
DateFormat df = getDateFormat();
return df.parse(dateStr);
} catch (ParseException e) {
//////////////////////////////////////////
try {//这里加入了StdDateFormat的parse
return new StdDateFormat().parse(dateStr);
} catch (ParseException e1) {
throw new IllegalArgumentException(String.format(
"Failed to parse Date value '%s': %s", dateStr, e.getMessage()));
}
//////////////////////////////////////////
}
}
虽然做法有些粗暴,一时半会儿也没什么其他办法,算是能用吧。