用Gson解析日期字符串在不同环境下结果不同

问题产生

在和上游系统接口对接的过程中,在生产环境上,发现对于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在处理时间格式时,目前没发现转换错误问题,可以尝试使用。

 

总结

坑谁都会踩到,踩到坑,你直接爬上来,头都不回就走了,下次碰到这个坑,你依然毫无防备的继续跳下去。反之,你爬上来后,围着这个坑转上那么几圈,蹲下去看上一番,这个坑是怎么造成的,我相信,等你下次在碰到它之前好几米远,你就开始找可以绕的路了。

学而不思则罔,思而不学则殆

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值