本次文章的主要内容

  • TypeAdapter
  • JsonSerializerJsonDeserializer
  • TypeAdapterFactory
  • @JsonAdapter 注解
  • TypeAdapterJsonSerializerJsonDeserializer 对比
  • TypeAdapter 实例
  • 结语

一、TypeAdapter

TypeAdapter 是 Gson 自 2.0(源码注释上说的是 2.1)开始版本提供的一个抽象类,用于接管某种类型的序列化和反序列化过程,包含两个注要方法 write(JsonWriter, T)read(JsonReader) 其它的方法都是 final 方法并最终调用这两个抽象方法。

  1. public abstract class TypeAdapter<T> {
  2. public abstract void write(JsonWriter out, T value) throws IOException;
  3. public abstract T read(JsonReader in) throws IOException;
  4. // 其它 final 方法就不贴出来了,包括 toJson、toJsonTree、toJson 和 nullSafe 方法。
  5. }

注意TypeAdapter 以及 JsonSerializerJsonDeserializer 都需要与 GsonBuilder.registerTypeAdapterGsonBuilder.registerTypeHierarchyAdapter 配合使用,下面将不再重复说明。

使用示例:

  1. User user = new User("怪盗kidou", 24);
  2. user.emailAddress = "ikidou@example.com";
  3. Gson gson = new GsonBuilder()
  4. // 为 User 注册 TypeAdapter
  5. .registerTypeAdapter(User.class, new UserTypeAdapter())
  6. .create();
  7. System.out.println(gson.toJson(user));

UserTypeAdapter 的定义:

  1. public class UserTypeAdapter extends TypeAdapter<User> {
  2. @Override
  3. public void write(JsonWriter out, User value) throws IOException {
  4. out.beginObject();
  5. out.name("name").value(value.name);
  6. out.name("age").value(value.age);
  7. out.name("email").value(value.email);
  8. out.endObject();
  9. }
  10. @Override
  11. public User read(JsonReader in) throws IOException {
  12. User user = new User();
  13. in.beginObject();
  14. while (in.hasNext()) {
  15. switch (in.nextName()) {
  16. case "name":
  17. user.name = in.nextString();
  18. break;
  19. case "age":
  20. user.age = in.nextInt();
  21. break;
  22. case "email":
  23. case "email_address":
  24. case "emailAddress":
  25. user.email = in.nextString();
  26. break;
  27. }
  28. }
  29. in.endObject();
  30. return user;
  31. }
  32. }

当我们为 User.class 注册了 TypeAdapter 之后,只要是操作 User.class 那些之前介绍的 @SerializedNameFieldNamingStrategySinceUntilExpose 通通都黯然失色,失去了效果,只会调用我们实现的 UserTypeAdapter.write(JsonWriter, User) 方法,我想怎么写就怎么写。

再说一个场景,在该系列的第一篇文章就说到了 Gson 有一定的容错机制,比如将字符串 "24" 转成 int24,但如果有些情况下给你返了个空字符串怎么办(有人给我评论问到这个问题)?虽然这是服务器端的问题,但这里我们只是做一个示范。

int 型会出错是吧,根据我们上面介绍的,我注册一个 TypeAdapter 把序列化和反序列化的过程接管不就行了?

  1. Gson gson = new GsonBuilder()
  2. .registerTypeAdapter(Integer.class, new TypeAdapter<Integer>() {
  3. @Override
  4. public void write(JsonWriter out, Integer value) throws IOException {
  5. out.value(String.valueOf(value));
  6. }
  7. @Override
  8. public Integer read(JsonReader in) throws IOException {
  9. try {
  10. return Integer.parseInt(in.nextString());
  11. } catch (NumberFormatException e) {
  12. return -1;
  13. }
  14. }
  15. })
  16. .create();
  17. System.out.println(gson.toJson(100)); // 结果:"100"
  18. System.out.println(gson.fromJson("\"\"", Integer.class)); // 结果:-1

注意:测试空串的时候一定是 "\"\"" 而不是 """" 代表的是没有 JSON 串,"\"\"" 才代表 JSON 里的 ""

你说这一接管就要管两样好麻烦呀,我明明只想管序列化(或反列化)的过程的,另一个过程我并不关心,难道没有其它更简单的方法么? 当然有!就是接下来要介绍的 JsonSerializerJsonDeserializer

二、JsonSerializerJsonDeserializer

JsonSerializerJsonDeserializer 不用像 TypeAdapter 一样,必须要实现序列化和反序列化的过程,你可以据需要选择,如只接管序列化的过程就用 JsonSerializer ,只接管反序列化的过程就用 JsonDeserializer ,如上面的需求可以用下面的代码。

  1. Gson gson = new GsonBuilder()
  2. .registerTypeAdapter(Integer.class, new JsonDeserializer<Integer>() {
  3. @Override
  4. public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
  5. try {
  6. return json.getAsInt();
  7. } catch (NumberFormatException e) {
  8. return -1;
  9. }
  10. }
  11. })
  12. .create();
  13. System.out.println(gson.toJson(100)); // 结果:100
  14. System.out.println(gson.fromJson("\"\"", Integer.class)); // 结果:-1

下面是所有数字都转成序列化为字符串的例子:

  1. JsonSerializer<Number> numberJsonSerializer = new JsonSerializer<Number>() {
  2. @Override
  3. public JsonElement serialize(Number src, Type typeOfSrc, JsonSerializationContext context) {
  4. return new JsonPrimitive(String.valueOf(src));
  5. }
  6. };
  7. Gson gson = new GsonBuilder()
  8. .registerTypeAdapter(Integer.class, numberJsonSerializer)
  9. .registerTypeAdapter(Long.class, numberJsonSerializer)
  10. .registerTypeAdapter(Float.class, numberJsonSerializer)
  11. .registerTypeAdapter(Double.class, numberJsonSerializer)
  12. .create();
  13. System.out.println(gson.toJson(100.0f)); // 结果:"100.0"

注意registerTypeAdapter 必须使用包装类型,所以 int.classlong.classfloat.classdouble.class 这些基本类型是行不通的。同时不能使用父类来替上面的子类型,这也是为什么要分别注册而不直接使用 Number.class 的原因。

上面特别说明了 registerTypeAdapter 不行,那就是有其它方法可行咯?当然!换成registerTypeHierarchyAdapter 就可以使用 Number.class 而不用一个一个的单独注册啦!

**registerTypeAdapter****registerTypeHierarchyAdapter** 的区别

registerTypeAdapter registerTypeHierarchyAdapter
支持泛型 不支持泛型
不支持继承 支持继承

注意:如果一个被序列化的对象本身就带有泛型,且注册了相应的 TypeAdapter,那么必须调用 Gson.toJson(Object, Type),明确告诉 Gson 对象的类型。

三、TypeAdapterFactory

TypeAdapterFactory 见名知意,用于创建 TypeAdapter 的工厂类,通过对比 Type,确定有没有对应的 TypeAdapter,没有就返回 null,与 GsonBuilder.registerTypeAdapterFactory 配合使用。

  1. Gson gson = new GsonBuilder()
  2. .registerTypeAdapterFactory(new TypeAdapterFactory() {
  3. @Override
  4. public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
  5. return null;
  6. }
  7. })
  8. .create();

四、@JsonAdapter 注解

JsonAdapter 相较之前介绍的 SerializedNameFieldNamingStrategySinceUntilExpose 这几个注解都是比较特殊的,其它的几个都是用在 POJO 的字段上,而这一个是用在 POJO 类上的,接收一个参数,且必须是 TypeAdpaterJsonSerializerJsonDeserializer 这三个其中之一。

上面说 JsonSerializerJsonDeserializer 都要配合 GsonBuilder.registerTypeAdapter 使用,但每次使用都要注册也太麻烦了,JsonAdapter 就是为了解决这个痛点的。

使用方法(以 User 为例):

  1. @JsonAdapter(UserTypeAdapter.class) // 加在类上
  2. public class User {
  3. public User() {
  4. }
  5. public User(String name, int age) {
  6. this.name = name;
  7. this.age = age;
  8. }
  9. public User(String name, int age, String email) {
  10. this.name = name;
  11. this.age = age;
  12. this.email = email;
  13. }
  14. public String name;
  15. public int age;
  16. @SerializedName(value = "emailAddress")
  17. public String email;
  18. }

使用时不用再使用 GsonBuilder 去注册 UserTypeAdapter 了。

注意@JsonAdapter 仅支持 TypeAdapterTypeAdapterFactory(Gson 2.7开始已经支持 JsonSerializer/JsonDeserializer

  1. Gson gson = new Gson();
  2. User user = new User("怪盗kidou", 24, "ikidou@example.com");
  3. System.out.println(gson.toJson(user));
  4. // 结果:{"name":"怪盗kidou","age":24,"email":"ikidou@example.com"}
  5. // 为区别结果,特意把 email 字段与 @SerializedName 注解中设置的不一样

五、TypeAdapterJsonSerializerJsonDeserializer 对比

# TypeAdapter JsonSerializer、JsonDeserializer
引入版本 2.0 1.x
Stream API 支持 不支持,需要提前生成 JsonElement
内存占用 TypeAdapter
效率 TypeAdapter
作用范围 序列化 反序列化 序列化 反序列化

六、TypeAdapter 实例

注意:这里的 TypeAdapter 泛指 TypeAdapterJsonSerializerJsonDeserializer

这里的 TypeAdapter 上面讲了一个自动将 字符串形式的数值转换成int型时可能出现 空字符串的问题,下面介绍一个其它读者的需求: :::info 需求
服务器返回的数据中 data 字段类型不固定,比如请求成功 data 是一个 List,不成功的时候是 String 类型,这样前端在使用泛型解析的时候,怎么去处理呢? ::: 其实这个问题的原因主要由服务器端造成的,接口设计时没有没有保证数据的一致性,正确的数据返回姿势:同一个接口任何情况下不得改变返回类型,要么就不要返,要么就返空值,如 **null****[]****{}**

但这里还是给出解决方案:
方案一

  1. Gson gson = new GsonBuilder()
  2. .registerTypeHierarchyAdapter(List.class, new JsonDeserializer<List<?>>() {
  3. @Override
  4. public List<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
  5. if (json.isJsonArray()){
  6. // 这里要自己负责解析了
  7. Gson newGson = new Gson();
  8. return newGson.fromJson(json,typeOfT);
  9. }else {
  10. // 和接口类型不符,返回空 List
  11. return Collections.EMPTY_LIST;
  12. }
  13. }
  14. }).create();

方案二

  1. Gson gson = new GsonBuilder()
  2. .registerTypeHierarchyAdapter(List.class, new JsonDeserializer<List<?>>() {
  3. @Override
  4. public List<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
  5. if (json.isJsonArray()) {
  6. JsonArray array = json.getAsJsonArray();
  7. Type itemType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];
  8. List list = new ArrayList<>();
  9. for (int i = 0; i < array.size(); i++) {
  10. JsonElement element = array.get(i);
  11. Object item = context.deserialize(element, itemType);
  12. list.add(item);
  13. }
  14. return list;
  15. } else {
  16. // 和接口类型不符,返回空 List
  17. return Collections.EMPTY_LIST;
  18. }
  19. }
  20. }).create();

要注意的点

  • 必须使用 registerTypeHierarchyAdapter 方法,不然对 List 的子类无效,但如果 POJO 中都是使用 List,那么可以使用 registerTypeAdapter
  • 对于是数组的情况,需要创建一个新的 Gson,不可以直接使用 context,不然 gson 又会调我们自定义的 JsonDeserializer 造成递归调用,方案二没有重新创建 Gson,那么就需要提取出 List<E>E 的类型,然后分别反序列化适合为 E 手动注册了 TypeAdaper 的情况。
  • 从效率上推荐方案二,免去重新实例化 Gson 和注册其它 TypeAdapter 的过程。

结语

Gson 系列总算是完成了,感觉写得越来越差了,我怕我写得太啰嗦,也不能总是大片大片的贴代码,所以可能有的地方写得并不详细,排版也不美观,但都些都不重点,重点是 Gson 里我们能用上的都一一介绍一遍,只要你确确实实把我这几篇文章上的内容都学会的话,以后 Gson 上的任何问题都不再是问题,当然可能很多内容对于实际的开发中用的并不多,但下次有什么疑难杂症就难不倒你了。

本系列不提供 Demo 源码,最重要的是自己实验。

写一篇文章还是要花不少时间和精力,要写示例、调式、组织语言、码字等等,加上关注的人在慢慢的增加的同时既给了我动力也给我不少压力,如有纰漏或者更好的例子都可以和我交流。


后期预告

之前有人给我评论说 出一点 retrofit 相关内容,我想了想,出是会出,但在此之前我想先出大概3~4篇文章用于介绍 泛型、反射、注解和 HTTP 的相关内容,当你确实掌握之后,我打包票你只需要看一遍 Retrofit 官方教程的代码示例,都不用看其它英文说明,就可以轻松玩转 Retrofit。

不服来战!