今天在线上发现一个问题,在使用Jackson进行时间的反序列化时,配置的 @JsonFormat 没有生效
查看源码发现,Jackson在反序列化时间时,会判断json字段值类型,如下:
由于在我们服务里,前端传时间值到后端时采用了时间戳的方式,json值被判断为数字类型,所以Jackson在反序列化时直接简单粗暴的方式处理,将时间戳转换为Date类型:
为了能够按照正确的格式解析时间,抹去后面的时间点,精确到日,只好自定义一个时间解析器。自定义的时间解析器很好实现,网上已经有很多实例代码,只需要继承 JsonDeserializer<T> 就可以。问题的关键点在于,如何获取到注解上的时间格式,按照注解上的格式去解析,否则每个解析器的实现只能使用一种固定的格式去解析时间。
1. 所以第一步是获取注解上配置的信息
想要获取字段对应的注解信息,只有找到相应的字段,然后通过字段属性获取注解信息,再通过注解信息获取配置的格式。但找了很久,也没有在既有的参数里找到获取相关字段的方法,只能去翻看源码,最后在这里发现了获取字段信息的方法以及解析器的生成过程,源代码如下:
第一个红框表示解析器是在这里生成的,第二个红框就是获取注解信息的地方
2. 注解获取以后便创建自定义的时间解析器
猜想,我们可不可以也实现这个类,重写生成解析器的方法?那就试试呗~ 我们在自定义的时间解析器上同样实现这个类,重写了生成时间解析器的方法,并初始化一些自定义的信息供解析时间使用(当然猜想是正确的,因为官方就是这么搞的,只是官方的是一个内部类实现的),具体代码如下:
时间解析器代码:
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.util.StdDateFormat;
import com.google.common.collect.Lists;
import com.tujia.rba.framework.core.remote.api.BizErrorCode;
import com.tujia.rba.framework.core.remote.api.BizException;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author 无名小生 Date: 2019-02-19 Time: 19:00
* @version $Id$
*/
public class DateJsonDeserializer extends JsonDeserializer<Date> implements ContextualDeserializer {
private final static Logger logger = LoggerFactory.getLogger(DateJsonDeserializer.class);
private final static List<String> FORMATS = Lists.newArrayList(
"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyyMMdd-HHmmss", "yyyy-MM-dd", "MM-dd", "HH:mm:ss", "yyyy-MM"
);
public final DateFormat df;
public final String formatString;
public DateJsonDeserializer() {
this.df = null;
this.formatString = null;
}
public DateJsonDeserializer(DateFormat df) {
this.df = df;
this.formatString = "";
}
public DateJsonDeserializer(DateFormat df, String formatString) {
this.df = df;
this.formatString = formatString;
}
@Override
public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
try {
String dateValue = p.getText();
if (df == null || StringUtils.isEmpty(dateValue)) {
return null;
}
logger.info("使用自定义解析器解析字段:{}:时间:{}",p.getCurrentName(),p.getText());
Date date;
if (StringUtils.isNumeric(dateValue)){
date = new Date(Long.valueOf(dateValue));
}else {
String[] patterns = FORMATS.toArray(new String[0]);
date = DateUtils.parseDate(p.getText(),patterns);
}
return df.parse(df.format(date));
} catch (ParseException | SecurityException e) {
logger.error("JSON反序列化,时间解析失败", e);
throw new BizException(BizErrorCode.UNEXPECTED_ERROR);
}
}
@Override
public JsonDeserializer<