问题形容
近期有一个老我的项目在测试环境中频繁呈现了GSON反序列化工夫问题,谬误堆栈如下所示:
Exception in thread "main" com.google.gson.JsonSyntaxException: 2021-05-14 14:59:37 at com.google.gson.internal.bind.DateTypeAdapter.deserializeToDate(DateTypeAdapter.java:81) at com.google.gson.internal.bind.DateTypeAdapter.read(DateTypeAdapter.java:66) at com.google.gson.internal.bind.DateTypeAdapter.read(DateTypeAdapter.java:41) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:93) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:172) at com.google.gson.Gson.fromJson(Gson.java:795) at com.google.gson.Gson.fromJson(Gson.java:761) at com.google.gson.Gson.fromJson(Gson.java:710) at com.google.gson.Gson.fromJson(Gson.java:682) at com.gson.GsonDate.main(GsonDate.java:17)Caused by: java.text.ParseException: Unparseable date: "2021-05-14 14:59:37" at java.text.DateFormat.parse(DateFormat.java:366) at com.google.gson.internal.bind.DateTypeAdapter.deserializeToDate(DateTypeAdapter.java:79) ... 9 more
谬误形容也很具体,就是GSON在反序列化一段Json串的时候,因为某个工夫字符串无奈反序列化,导致最终整个Json反序列化失败;
背景阐明
因为此零碎数据量比拟大,所有会将半年前的数据归档到Hbase中,归档的时候会将数据库中的数据序列化为json格局,而后保留到Hbase中;如果是近期半年的数据会间接查询数据库,如果是很早的数据才会查问Hbase,所以呈现的概率比拟低;
问题剖析
本地重现
为了不便剖析,间接把Json串拷贝到本地,而后再本地进行重现,再进行问题剖析,Json串比拟长,这里应用如下Json串代替:
{"date":"2021-05-14 14:59:37"}
筹备相干代码如下所示:
public class GsonDate { public static void main(String[] args) { String json = "{\"date\":\"2021-05-14 14:59:37\"}"; GsonDateBean date = new Gson().fromJson(json, GsonDateBean.class); System.out.println(date); }}@Dataclass GsonDateBean { private Date date;}
执行的后果是能够反序列胜利,并没有呈现下面的谬误,为了找出起因,这里须要剖析一下Gson工夫转换的相干源码;
源码剖析
Gson工夫转换的源码还是比较简单的,DateTypeAdapter
局部代码如下所示:
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; } 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); } }
Gson筹备了三个DateFormat
,别离是:localFormat,enUsFormat,iso8601Format;转换的时候也是依照这个程序进行转换,哪个能转换胜利就间接返回,以上呈现问题阐明三种DateFormat
都没有转换胜利;本地调试能够间接Debug进来,能够发现间接应用localFormat就转换胜利了,并且能够别离查看每个的pattern
;
- localFormat:yyyy-M-d H:mm:ss
- enUsFormat:MMM d, yyyy h:mm:ss a
- iso8601Format:yyyy-MM-dd'T'HH:mm:ss'Z'
以上的日期格局完全符合yyyy-M-d H:mm:ss
格局,所以能够间接转换胜利;能够发现localFormat其实是和本地零碎的语言环境无关,所以会呈现本地运行后果和服务器运行后果不统一;
再次重现
能够间接通过代码设置语言环境,把环境设置为Locale.US
public class GsonDate { public static void main(String[] args) { System.out.println("默认:"+Locale.getDefault()); System.out.println("重置语言环境:Locale.US"); Locale.setDefault(Locale.US); String json = "{\"date\":\"2021-05-14 14:59:37\"}"; GsonDateBean date = new Gson().fromJson(json, GsonDateBean.class); System.out.println(date); }}
运行以上代码,呈现了和服务器一样的反序列工夫问题:
默认:zh_CN重置语言环境:Locale.USException in thread "main" com.google.gson.JsonSyntaxException: 2021-05-14 14:59:37 at com.google.gson.internal.bind.DateTypeAdapter.deserializeToDate(DateTypeAdapter.java:81)
能够发现咱们本地的环境个别都是zh_CN
,对应Locale.CHINA
;
问题解决
系统配置
能够间接扭转零碎语言环境,liunx能够间接在/etc/sysconfig/i18n
中配置:
英文版零碎:LANG="en_US.UTF-8"中文版零碎:LANG="zh_CN.UTF-8"
能够查看以后配置的语言环境:
[root@Centos ~]# echo $LANGen_US.UTF-8
代码实现
能够给Gson设置默认的日志转换格局:
Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();GsonDateBean date = gson.fromJson(json, GsonDateBean.class);
扩大
同样的如果应用其余Json序列化工具,比方fastjson
是否也有这样的问题那,能够简略做一个测试:
Locale.setDefault(Locale.US);String json = "{\"date\":\"2021-05-14 14:59:37\"}";String json2 = "{\"date\":\"2021年05月14日 14:59:37\"}";JacksonDateBean date = JSON.parseObject(json, JacksonDateBean.class);
后果是不仅yyyy-MM-dd HH:mm:ss
格局能被解析,蕴含中文的年月日
都能够被解析;如果查看相干源码能够发现,fastjson
并没有间接应用DateFormat
去做日期格局转换,而是实现了ISO 8601规范,并且提供了中国常见日期格局的反对;具体能够间接查看源码JSONScanner
中的scanISO8601DateIfMatch
办法;
另外一点须要阐明的是以上GSON应用的是2.2.2
版本,最新版本2.8.6
版本中同样提供了对ISO 8601规范
的反对,具体能够查看ISO8601Utils
类。
感激关注
能够关注微信公众号「回滚吧代码」,第一工夫浏览,文章继续更新;专一Java源码、架构、算法和面试。