Fastjson内置的日期类型解析器(com.alibaba.fastjson.serializer.DateCodec)只支持ISO8601以及SQL标准日期格式(yyyy-MM-dd HH:mm:ss
),但日期格式的常用种类也不少:比如 Date.toString()输出的Tuesday February 27 10:43:27 CST 2024
这样的格式。除非在使用注解方式指定日期格式。否则Fastjson不能解析这样的格式。
FastjsonDateDeserializer
如果要实现自适应的各种日期解析,就要自己实现日期解析器,
以下为参照com.alibaba.fastjson.parser.deserializer.AbstractDateDeserializer.deserialze方法实现的自适应日期解析器代码
import java.lang.reflect.Type;
import java.util.Calendar;
import java.util.Date;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.parser.DefaultJSONParser;
import com.alibaba.fastjson.parser.JSONLexer;
import com.alibaba.fastjson.parser.JSONScanner;
import com.alibaba.fastjson.parser.JSONToken;
import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;
import static net.gdface.utils.DateSupport.parseAsDate;
import static net.gdface.utils.DateSupport.castToDate;
/**
* 日期类型FASTJSON反序列化实现,
* 相比默认的反序列化器 {@link com.alibaba.fastjson.serializer.DateCodec} 支持更多日期格式
* @author guyadong
*/
public class FastjsonDateDeserializer implements ObjectDeserializer {
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
JSONLexer lexer = parser.lexer;
if(!(type instanceof Class) || !(Date.class.isAssignableFrom((Class)type) || Calendar.class.isAssignableFrom((Class)type))) {
throw new IllegalArgumentException("type required Date or Calendar");
}
switch(lexer.token()) {
case JSONToken.LITERAL_INT:{
long millis = lexer.longValue();
lexer.nextToken(JSONToken.COMMA);
try {
return (T) castToDate(millis,(Class)type);
} catch (Exception e) {
throw new JSONException(e.getMessage(),e);
}
}
case JSONToken.LITERAL_STRING:{
String strVal = lexer.stringVal();
lexer.nextToken(JSONToken.COMMA);
try(JSONScanner iso8601Lexer = new JSONScanner(strVal)){
if (iso8601Lexer.scanISO8601DateIfMatch(false)) {
Calendar calendar = iso8601Lexer.getCalendar();
return (T) castToDate(calendar,(Class)type);
}else {
Object parsed = parseAsDate(strVal, (Class)type,0,1,2,3,4);
if(parsed != null) {
return (T) parsed;
}
throw new JSONException("FAIL parse date: "+strVal);
}
}
}
case JSONToken.NULL:
lexer.nextToken();
return null;
default:
throw new JSONException("parse error");
}
}
public int getFastMatchToken() {
return JSONToken.LITERAL_INT;
}
}
DateSupport
上面的代码中调用的parseAsDate,castToDate
来自DateSupport
,它在PATTERNS数组中定义常用的日期格式。对于日期字符串会顺序尝试使用这些格式来解析直到能够成功解析为止。
代码如下:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Set;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicReference;
import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
/**
* 日期工具类<br>
* copy from sql2java-base gu.sql2java.utils.DateSupport
* @author guyadong
* @since 2.8.8
*/
public class DateSupport {
/**
* pattern for received date processing.
*/
private static final String[] PATTERNS = new String[]
{
"yyyy-MM-dd'T'HH:mm:ss.SSSZ", /** ISO8601时间格式 */
"yyyy-MM-dd HH:mm:ss", /** 用于SQL语句的时间戳格式转换格式 */
"yyyy-MM-dd", /** 日期转换格式 */
"yyyy-MM-d HH:mm:ss",
"yyyy-MM-dd HH:mm:ss Z",
"yyyy-MM-dd HH:mm:ss z",
"HH:mm:ss",
"EEE MMM dd HH:mm:ss z yyyy", /** Tue Feb 27 10:43:27 CST 2024 */
"EEE, dd MMM yyyy HH:mm:ss '-'S '('z')'",
"EEE, dd MMM yyyy HH:mm:ss '+'S '('z')'",
"EEE, dd MMM yyyy HH:mm:ss '-'S",
"EEE, dd MMM yyyy HH:mm:ss '+'S",
"EEE, dd MMM yyyy HH:mm:ss z",
"EEE, dd MMM yyyy HH:mm:ss Z",
"EEE, dd MMM yyyy HH:mm:ss",
"EEE, d MMM yyyy HH:mm:ss '-'S '('z')'",
"EEE, d MMM yyyy HH:mm:ss '+'S '('z')'",
"EEE, d MMM yyyy HH:mm:ss '-'S",
"EEE, d MMM yyyy HH:mm:ss '+'S",
"EEE, d MMM yyyy HH:mm:ss z",
"EEE, d MMM yyyy HH:mm:ss Z",
"EEE, d MMM yyyy HH:mm:ss",
"EEE, dd MMM yy HH:mm:ss '-'S '('z')'",
"EEE, dd MMM yy HH:mm:ss '+'S '('z')'",
"EEE, dd MMM yy HH:mm:ss '-'S",
"EEE, dd MMM yy HH:mm:ss '+'S",
"EEE, dd MMM yy HH:mm:ss z",
"EEE, dd MMM yy HH:mm:ss Z",
"EEE, dd MMM yy HH:mm:ss",
"EEE, d MMM yy HH:mm:ss '-'S '('z')'",
"EEE, d MMM yy HH:mm:ss '+'S '('z')'",
"EEE, d MMM yy HH:mm:ss '-'S",
"EEE, d MMM yy HH:mm:ss '+'S",
"EEE, d MMM yy HH:mm:ss z",
"EEE, d MMM yy HH:mm:ss Z",
"EEE, d MMM yy HH:mm:ss",
"dd MMM yyyy HH:mm:ss '-'S",
"dd MMM yyyy HH:mm:ss '+'S",
"dd MMM yyyy HH:mm:ss '-'S '('z')'",
"dd MMM yyyy HH:mm:ss '+'S '('z')'",
"dd MMM yyyy HH:mm:ss z",
"dd MMM yyyy HH:mm:ss Z",
"dd MMM yyyy HH:mm:ss",
"dd MMM yyy HH:mm:ss '-'S",
"dd MMM yyy HH:mm:ss '+'S",
"dd MMM yyy HH:mm:ss '-'S '('z')'",
"dd MMM yyy HH:mm:ss '+'S '('z')'",
"dd MMM yyy HH:mm:ss z",
"dd MMM yyy HH:mm:ss Z",
"dd MMM yyy HH:mm:ss",
"yyyy.MM.dd HH:mm:ss z",
"yyyy.MM.dd HH:mm:ss Z",
"yyyy.MM.d HH:mm:ss z",
"yyyy.MM.d HH:mm:ss Z",
"yyyy.MM.dd HH:mm:ss",
"yyyy.MM.d HH:mm:ss",
"yy.MM.dd HH:mm:ss z",
"yy.MM.dd HH:mm:ss Z",
"yy.MM.d HH:mm:ss z",
"yy.MM.d HH:mm:ss Z",
"yy.MM.dd HH:mm:ss",
"yy.MM.d HH:mm:ss",
"yyyy MM dd HH:mm:ss",
"yyyy MM d HH:mm:ss",
"yyyy MM dd HH:mm:ss z",
"yyyy MM dd HH:mm:ss Z",
"yyyy MM d HH:mm:ss z",
"yyyy MM d HH:mm:ss Z",
"yy MM dd HH:mm:ss",
"yy MM d HH:mm:ss",
"yy MM dd HH:mm:ss z",
"yy MM dd HH:mm:ss Z",
"yy MM d HH:mm:ss z",
"yy MM d HH:mm:ss Z",
"yy-MM-dd HH:mm:ss z",
"yy-MM-dd HH:mm:ss Z",
"yy-MM-d HH:mm:ss z",
"yy-MM-d HH:mm:ss Z",
"yy-MM-dd HH:mm:ss",
"yy-MM-d HH:mm:ss",
"dd MMM yyyy",
"d MMM yyyy",
"dd.MMM.yyyy",
"d.MMM.yyyy",
"dd-MMM-yyyy",
"d-MMM-yyyy",
"dd MM yyyy",
"d MM yyyy",
"dd.MM.yyyy",
"d.MM.yyyy",
"dd-MM-yyyy",
"d-MM-yyyy",
"yyyy MM dd",
"yyyy MM d",
"yyyy.MM.dd",
"yyyy.MM.d",
"yyyy-MM-d",
"dd MMM yy",
"d MMM yy",
"dd.MMM.yy",
"d.MMM.yy",
"dd-MMM-yy",
"d-MMM-yy",
"dd MM yy",
"d MM yy",
"dd.MM.yy",
"d.MM.yy",
"dd-MM-yy",
"d-MM-yy",
"yy MMM dd",
"yy MMM d",
"yy.MMM.d",
"yy-MMM-dd",
"yy-MMM-d",
"yy.MMM.dd",
// ex: Wed 19, Feb 2003
"EEE dd, MMM yyyy",
// ex: Wed 19, Feb 03
"EEE dd, MMM yy"
};
/** ISO8601 date time pattern */
static final String ISO8601_FORMATTER_STR = PATTERNS[0];
/** 用于SQL语句的时间戳格式转换格式 */
public static final String TIMESTAMP_FORMATTER_STR = PATTERNS[1];
/** 日期转换格式 */
static final String DATE_FORMATTER_STR = PATTERNS[2];
/**
* get a date from a date string representation in one of the registered formats
* @param strDate the date as string.
* @param pattern [out] if not null, return pattern string or null if (null or empty) or correct pattern was not found
* @param excludeIndexs excluded pattern index
* @return Date object ,otherwise null If (null or empty) or correct pattern was not found
*/
public static java.util.Date getDateFromString(String strDate,AtomicReference<String>pattern,int...excludeIndexs)
{
java.util.Date dReceivedDate = null;
if (strDate == null || strDate.trim().equals("null")) {
return dReceivedDate;
} else {
strDate = strDate.trim();
}
Set<Integer> exidx =
Sets.newHashSet(Ints.asList(null == excludeIndexs ? new int[0]: excludeIndexs));
SimpleDateFormat pSimpleDateFormat = new SimpleDateFormat("",Locale.ENGLISH);
if (!strDate.isEmpty())
{
for (int i=0; i<PATTERNS.length; i++)
{
if(exidx.contains(i)) {
continue;
}
try
{
pSimpleDateFormat.applyPattern(PATTERNS[i]);
dReceivedDate = pSimpleDateFormat.parse(strDate);
if (dReceivedDate == null)
{
continue;
}
if( null !=pattern) {
pattern.set(PATTERNS[i]);
}
return dReceivedDate;
}
catch (ParseException pe)
{
; // ignore this format try the next one
}
}
}
return dReceivedDate;
}
/**
* get a date from a date string representation in one of the registered formats
* @param strDate the date as string.
* @return Date object ,otherwise null If (null or empty) or correct pattern was not found
*/
public static java.util.Date getDateFromString(String strDate)
{
return getDateFromString(strDate,null);
}
/**
* get a date from a date string representation in one of the registered formats
* @param dateStr the date as string.
* @param targetClass
* @return Date object ,otherwise null If (null or empty) or correct pattern was not found
*/
public static <D extends Date> D parseDateString(String dateStr, Class<D> targetClass) {
if(null != dateStr && null != targetClass)
{
Date date = null;
try {
date = new SimpleDateFormat(ISO8601_FORMATTER_STR).parse(dateStr);
} catch (ParseException e3) {
try {
date = java.sql.Timestamp.valueOf(dateStr);
} catch (IllegalArgumentException e) {
try {
date =java.sql.Date.valueOf(dateStr);
} catch (IllegalArgumentException e1) {
try {
date =java.sql.Time.valueOf(dateStr);
} catch (IllegalArgumentException e2) {
date = getDateFromString(dateStr);
}
}
}
}
return castToDate(date,targetClass);
}
return null;
}
/**
* get a date from a date string representation in one of the registered formats
* @param input the date as string.
* @param targetClass Date or Calendar or subclass required
* @param excludeIndexs excluded pattern index
* @return Date object ,otherwise null If (null or empty) or correct pattern was not found
*/
public static <D> D parseAsDate(String input, Class<D> targetClass,int...excludeIndexs) {
if(null != input && null != targetClass){
Date date = getDateFromString(input,null,excludeIndexs);
return castToDate(date,targetClass);
}
return null;
}
/**
* convert {@link Date} to ISO8601 date time format string
* @param date
* @return ISO8601 date time format string or null if date is null
*/
public static String toISO8601String(Date date){
return null == date ? null : new SimpleDateFormat(ISO8601_FORMATTER_STR).format(date);
}
/**
* format {@link Datec} to string
* @param date
* @param format date time format string,use ISO8601 format if null
* @return ISO8601 date time format string or null if date is null
*/
public static String formatDate(Date date, String format){
return null == date ? null : new SimpleDateFormat(null == format ? ISO8601_FORMATTER_STR : format ).format(date);
}
/**
* Verify that the string represantes the date with one of the registered formats
* @param strDate the date as string.
* @return boolean "true" if the string represantes the date in one of the registed formats.
*/
public static boolean isDate(String strDate)
{
return null != getDateFromString(strDate);
}
/**
* Verify that the string represantes the date with one of the registered formats
* @param strDate the date as string.
* @return boolean "true" if the string represantes the date in one of the registed formats.
* @since 3.25.0
*/
public static String patternOf(String strDate)
{
AtomicReference<String> p = new AtomicReference<>();
getDateFromString(strDate,p);
return p.get();
}
/**
* 将对象转为指定的日期类型
* @param <F> 原类型 String,Number,java.util.Date or Calendar or subclass
* @param <T> 目标类型 java.util.Date or Calendar or subclass
* @param from
* @param targetClass
*/
@SuppressWarnings("unchecked")
public static <F,T>T castToDate(F from,Class<T>targetClass) {
if(null != from && null != targetClass){
if(targetClass.isInstance(from)){
return targetClass.cast(from);
}else if(from instanceof Date) {
Date date = (Date)from;
if(Date.class.isAssignableFrom(targetClass)) {
try {
return targetClass.getConstructor(long.class).newInstance(date.getTime());
} catch (Exception e) {
throw new IllegalArgumentException("UNSUPPORTED Date type:"+targetClass.getName(),e);
}
}else if (targetClass == Calendar.class) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
return (T) calendar;
}else if(Calendar.class.isAssignableFrom(targetClass)) {
try {
Calendar calendar = (Calendar) targetClass.newInstance();
calendar.setTime(date);
return (T) calendar;
} catch (Exception e) {
throw new IllegalArgumentException("UNSUPPORTED Date type:"+targetClass.getName(),e);
}
}else {
throw new IllegalArgumentException("UNSUPPORTED Date type:"+targetClass.getName());
}
}else if(from instanceof Calendar){
return castToDate(((Calendar)from).getTime(),targetClass);
}else if(from instanceof Number){
return castToDate((new Date(((Number)from).longValue())),targetClass);
}else if(from instanceof String){
return castToDate(getDateFromString((String)from,null),targetClass);
}else {
throw new IllegalArgumentException("UNSUPPORTED Date type:"+from.getClass().getName());
}
}
return null;
}
}
示例代码:
@Test
public void test6FastjsonDateDeserializer() {
try {
String input="\"Tuesday February 27 10:43:27 CST 2024\"";
// String input="\"2024-02-27 15:01:31+0800\"";
// String input="\"2024-02-27\"";
// ParserConfig.global.putDeserializer(Date.class, new FastjsonDateDeserializer());
ParserConfig.global.putDeserializer(java.sql.Date.class, new FastjsonDateDeserializer());
Date parsed = JSON.parseObject(input, java.sql.Date.class);
log("parsed {}",parsed);
} catch (Throwable e) {
e.printStackTrace();
fail(e.getMessage());
}
}
以上实现的完整代码参见:
https://gitee.com/l0km/common-java/blob/master/common-base2/src/main/java/net/gdface/json/FastjsonDateDeserializer.java
测试代码参见 :
https://gitee.com/l0km/common-java/blob/master/common-base2/src/test/java/net/gdface/json/FastjsonTest.java