问题产生
在和上游系统接口对接的过程中,在生产环境上,发现对于2018-7-25 09:32:26.0这样的字符串,Gson数据解析错误,com.google.gson.JsonSyntaxException,起初天真的以为是yyyy-MM-dd HH:mm:ss.0后边的.0搞的鬼,后来进一步确认后,才发现问题没有想象的那么简单,同样的代码,在本地环境上去解析相同格式的字符串可以正常解析,而在Linux服务器上确解析失败。
源码分析
跟着报错堆栈网上找,发现Gson(version:2.3或2.5)在处理日期/时间格式的json字符串时,通过下面这个日期类型适配器来进行转换
public final class DateTypeAdapter extends TypeAdapter<Date> {
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
return typeToken.getRawType() == Date.class ? (TypeAdapter<T>) new DateTypeAdapter() : null;
}
};
private final DateFormat enUsFormat
= DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US);
private final DateFormat localFormat
= DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT);
private final DateFormat iso8601Format = buildIso8601Format();
private static DateFormat buildIso8601Format() {
DateFormat iso8601Format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
iso8601Format.setTimeZone(TimeZone.getTimeZone("UTC"));
return iso8601Format;
}
@Override public Date read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
return deserializeToDate(in.nextString());
}
private synchronized Date deserializeToDate(String json) {
try {
return localFormat.parse(json);
} catch (ParseException ignored) {
}
try {
return enUsFormat.parse(json);
} catch (ParseException ignored) {
}
try {
return iso8601Format.parse(json);
} catch (ParseException e) {
throw new JsonSyntaxException(json, e);
}
}
...
}
可以看到在deserializeToDate这个反序列化json到Date的方法中,Gson尝试进行了三种转换,分别用localFormat、enUsFormat、iso8601Format,这三种任意一种类型转换成功就可以。iso8601这种类型的时间格式,代码中已有它的格式说明
private static DateFormat buildIso8601Format() {
DateFormat iso8601Format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
iso8601Format.setTimeZone(TimeZone.getTimeZone("UTC"));
return iso8601Format;
}
对于localFormat、enUsFormat我们在开发环境下(windows操作系统下)的时间格式经测试如下
对于localFormat、enUsFormat我们在sit或生产环境下(Linux操作系统下)的时间格式经测试如下
可以看出在不同环境下localFormat的默认时间格式不同,这就导致yyyy-MM-dd HH:mm:ss这种常用时间格式在Linux系统下Gson解析失败。
解决方案
1、Gson在实例化时,如果不指定时间转换格式的话,它会采用上述3种格式去轮流尝试转换,如果不采用new Gson()这种形式去实例化,采用new GsonBuilder()方式去实例化Gson时,就可以设置很多转换格则,如下,我们指定好日期转换格式后,Gson就按我们指定的格式去进行日期转换。
public static final Gson GSON = new Gson();
public static final Gson GSON = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
2、fastjson在处理时间格式时,目前没发现转换错误问题,可以尝试使用。
总结
坑谁都会踩到,踩到坑,你直接爬上来,头都不回就走了,下次碰到这个坑,你依然毫无防备的继续跳下去。反之,你爬上来后,围着这个坑转上那么几圈,蹲下去看上一番,这个坑是怎么造成的,我相信,等你下次在碰到它之前好几米远,你就开始找可以绕的路了。
学而不思则罔,思而不学则殆