最近使用Gson
序列化HashMap
实例发现序列化结果为null
。这其实不是什么Bug
,后面我百度一下发现很多人都发现过这个问题,只是不知道出于什么考虑。
这是单元测试:
public class JsonTest {
@Test
public void testGsonMap() {
Map<String, Object> data = new HashMap<String, Object>() {{
put("xxx", "yyy");
}};
System.out.println(JsonUtils.toJsonString(data));
}
}
一般情况下我们也不会这样使用,这是同事给我反馈的,一开始还以为是我封装的适配多json
框架组件的Bug
。
其中JsonUtils#toJsonString
方法实际调用的是Gson#toJson
方法, 测试结果返回null
。但切换到jackson
框架是能够序列化成功的。
通过查看单元测试编译后的字节码发现“data”
变量实际上是一个匿名类实例。这个匿名类继承HashMap
,并且类名以$1结尾
。
经过调试后发现,Gson
会过滤类名以$
+数字结尾的匿名类实例,不会序列化此类型的实例。下面是调用栈:
com.google.gson.internal.Excluder#create
>com.google.gson.internal.Excluder#excludeClassChecks
>com.google.gson.internal.Excluder#isAnonymousOrLocal
>java.lang.Class#isAnonymousClass
当java.lang.Class#isAnonymousClass
方法返回false
时,这种类型的实例将不被序列化。
public final class Class<T> {
public boolean isAnonymousClass() {
return "".equals(getSimpleName());
}
}
isAnonymousClass
方法通过调用getSimpleName
方法比较SimpleName
是否是空串,然而getSimpleName
方法会去掉类名的$
符号以及跟随$
符号后面的数字。
在此案例中,data
变量的类型为com.xxx.JsonTest$1
,去掉包名com.xxx
,再去掉$
符号之前的JsonTest
后类名为$1
,去掉$
符号和后面的数字后,类名就是""
。
我试着在JsonTest
里面写一个内部类com.xxx.JsonTest$Objcet2
,getSimpleName
返回的是Objcet2
。所以,Gson
支持内部类,但不支持匿名内部类。
我也很难想到序列化使用匿名内部类的场景,但如果真的需要序列化匿名内部类,可以使用Gson.toJson(obj,type)
方法。