我女朋友是一个很细心也很贴心的人,在你调用 toJson() 方法进行序列化的时候,她会先判 null,防止抛出 NPE,再通过 getClass() 获取参数的类型,然后进行序列化。
public String toJson(Object src) { if (src == null) { return toJson(JsonNull.INSTANCE); } return toJson(src, src.getClass()); }
但是呢?对于泛型来说,getClass() 的时候会丢掉参数化类型。来看下面这个例子。
public class Foo<T> { T value; public void set(T value) { this.value = value; } public T get() { return value; } public static void main(String[] args) { Gson gson = new Gson(); Foo<Bar> foo = new Foo<Bar>(); Bar bar = new Bar(); foo.set(bar); String json = gson.toJson(foo); } } class Bar{ private int age = 10; private String name = "图灵"; }
假如你 debug 的时候,进入到 toJson() 方法的内部,就可以观察到。
foo 的实际类型为 Foo<Bar>,但我女朋友在调用 foo.getClass() 的时候,只会得到 Foo,这就意味着她并不知道 foo 的实际类型。
序列化的时候还好,反序列化的时候就无能为力了。
Foo<Bar> foo1 = gson.fromJson(json, foo.getClass());
Bar bar1 = foo1.get();
这段代码在运行的时候就报错了。
Exception in thread "main" java.lang.ClassCastException: class com.google.gson.internal.LinkedTreeMap cannot be cast to class com.itwanger.gson.Bar (com.google.gson.internal.LinkedTreeMap and com.itwanger.gson.Bar are in unnamed module of loader 'app')
at com.itwanger.gson.Foo.main(Foo.java:36)
默认情况下,泛型的参数类型会被转成 LinkedTreeMap,这显然并不是我们预期的 Bar,女朋友对此表示很无奈。
作为 Google 的亲儿子,我的血液里流淌着“贵族”二字,我又怎能忍心女朋友无助时的落寞。
于是,我在女朋友的体内植入了另外两种方法,带 Type 类型参数的:
toJson(Object src, Type typeOfSrc);
<T> T fromJson(String json, Type typeOfT);
这样的话,你在进行泛型的序列化和反序列化时,就可以指定泛型的参数化类型了。
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
String json = gson.toJson(foo,fooType);
Foo<Bar> foo1 = gson.fromJson(json, fooType);
Bar bar1 = foo1.get();
debug 进入 toJson() 方法内部查看的话,就可以看到 foo 的真实类型了。
fromJson() 在反序列化的时候,和此类似。
这样的话,bar1 就可以通过 foo1.get() 到了。
瞧,我考虑得多周全,女朋友都忍不住夸我了!
05、处理混合类型
你知道的,Java 不建议使用混合类型,也就是下面这种情况。
List list = new ArrayList();
list.add("沉默王二");
list.add(18);
list.add(new Event("gson", "google"));
Event 的定义如下所示:
class Event {
private String name;
private String source;
Event(String name, String source) {
this.name = name;
this.source = source;
}
}
由于 list 没有指定具体的类型,因此它里面可以存放各种类型的数据。这样虽然省事,我女朋友在序列化的时候也没问题,但反序列化的时候就要麻烦多了。
Gson gson = new Gson();
String json = gson.toJson(list);
System.out.println(json);
输出结果如下所示:
["沉默王二",18,{"name":"gson","source":"google"}]
1
反序列化的时候,就需要花点心思才能拿到 Event 对象。
JsonParser parser = new JsonParser();
JsonArray array = parser.parse(json).getAsJsonArray();
String message = gson.fromJson(array.get(0), String.class);
int number = gson.fromJson(array.get(1), int.class);
Event event = gson.fromJson(array.get(2), Event.class);
承认了,JsonParser 是我的前任。希望你不要喷我渣男,真不是我花心,是因为我们性格上有些不太适合。但我们仍然保持着朋友的关系,因为我们谁都没有错,只是代码更加规范了,已经很少有开发者使用混合类型了。
06、个性化定制
考虑到你是一个追求时髦的人,我一直对自己要求很高,力争能够满足你的所有需求。这种高标准的要求,让我女朋友对我是又爱又恨。
爱的是,我这种追求完美的态度;恨的是,她有时候力不从心,帮不上忙。
使用 toJson() 序列化 Java 对象时,返回的 JSON 字符串中没有空格,很紧凑。如果你想要打印更漂亮的 JSON 格式,你需要打电话给一个叫 GsonBuilder 的老板,让他进行一些定制,然后再把复刻版邮寄给你,就像我在使用指南中提到的那样。
public class Writer {
private int age = 18;
private String name = "沉默王二";
public static void main(String[] args) {
Writer writer = new Writer();
Gson gson = new Gson();
String json = gson.toJson(writer);
System.out.println(json);
Gson gson1 = new GsonBuilder().setPrettyPrinting().create();
String jsonOutput = gson1.toJson(writer);
System.out.println(jsonOutput);
}
}
来对比一下输出结果:
{"age":18,"name":"沉默王二"}
{
"age": 18,
"name": "沉默王二"
}
通过 setPrettyPrinting() 定制后,输出的格式更加层次化、立体化,字段与值之间有空格,每个不同的字段之间也会有换行。
之前提到了,默认情况下,我女朋友在序列化的时候会忽略 null 值的字段,如果不想这样的话,同样可以打电话给 GsonBuilder。
public class Writer {
private int age = 18;
private String name = null;
public static void main(String[] args) {
Writer writer = new Writer();
Gson gson = new Gson();
String json = gson.toJson(writer);
System.out.println(json);
Gson gson2 = new GsonBuilder().serializeNulls().create();
String jsonOutput2 = gson2.toJson(writer);
System.out.println(jsonOutput2);
}
}
来对比一下输出结果:
{"age":18}
{"age":18,"name":null}
1
2
通过 serializeNulls() 定制后,序列化的时候就不会再忽略 null 值的字段。
也许,你在序列化和反序列化的时候想要筛选一些字段,我也考虑到这种需求了,特意为你准备了几种方案,你可以根据自己的口味挑选适合你的。
第一种,通过 Java 修饰符。
你之前也看到了,使用 transient 关键字修饰的字段将不会参与序列化和反序列化。同样的,static 关键字修饰的字段也不会。如果你想保留这些关键字修饰的字段,可以这样做。
保留单种。
Gson gson = new GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT).create();
保留多种。
Gson gson = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT, Modifier.VOLATILE)
.create();
第二种,通过 @Expose 注解。
要使用 @Expose 注解,你需要先这样做:
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
再在需要序列化和反序列化的字段上加上 @Expose 注解,如果没加的话,该字段将会被忽略。
@Expose
private int age = 18;
07、心声
如果你还想了解更多的话,请来参观我的 GitHub 主页:
https://github.com/google/gson