本次文章的主要内容:
TypeAdapterJsonSerializer与JsonDeserializerTypeAdapterFactory@JsonAdapter注解TypeAdapter与JsonSerializer、JsonDeserializer对比TypeAdapter实例- 结语
一、TypeAdapter
TypeAdapter 是 Gson 自 2.0(源码注释上说的是 2.1)开始版本提供的一个抽象类,用于接管某种类型的序列化和反序列化过程,包含两个注要方法 write(JsonWriter, T) 和 read(JsonReader) 其它的方法都是 final 方法并最终调用这两个抽象方法。
public abstract class TypeAdapter<T> {public abstract void write(JsonWriter out, T value) throws IOException;public abstract T read(JsonReader in) throws IOException;// 其它 final 方法就不贴出来了,包括 toJson、toJsonTree、toJson 和 nullSafe 方法。}
注意:TypeAdapter 以及 JsonSerializer 和 JsonDeserializer 都需要与 GsonBuilder.registerTypeAdapter 或 GsonBuilder.registerTypeHierarchyAdapter 配合使用,下面将不再重复说明。
使用示例:
User user = new User("怪盗kidou", 24);user.emailAddress = "ikidou@example.com";Gson gson = new GsonBuilder()// 为 User 注册 TypeAdapter.registerTypeAdapter(User.class, new UserTypeAdapter()).create();System.out.println(gson.toJson(user));
UserTypeAdapter 的定义:
public class UserTypeAdapter extends TypeAdapter<User> {@Overridepublic void write(JsonWriter out, User value) throws IOException {out.beginObject();out.name("name").value(value.name);out.name("age").value(value.age);out.name("email").value(value.email);out.endObject();}@Overridepublic User read(JsonReader in) throws IOException {User user = new User();in.beginObject();while (in.hasNext()) {switch (in.nextName()) {case "name":user.name = in.nextString();break;case "age":user.age = in.nextInt();break;case "email":case "email_address":case "emailAddress":user.email = in.nextString();break;}}in.endObject();return user;}}
当我们为 User.class 注册了 TypeAdapter 之后,只要是操作 User.class 那些之前介绍的 @SerializedName、FieldNamingStrategy、Since、Until、Expose 通通都黯然失色,失去了效果,只会调用我们实现的 UserTypeAdapter.write(JsonWriter, User) 方法,我想怎么写就怎么写。
再说一个场景,在该系列的第一篇文章就说到了 Gson 有一定的容错机制,比如将字符串 "24" 转成 int 的 24,但如果有些情况下给你返了个空字符串怎么办(有人给我评论问到这个问题)?虽然这是服务器端的问题,但这里我们只是做一个示范。
int 型会出错是吧,根据我们上面介绍的,我注册一个 TypeAdapter 把序列化和反序列化的过程接管不就行了?
Gson gson = new GsonBuilder().registerTypeAdapter(Integer.class, new TypeAdapter<Integer>() {@Overridepublic void write(JsonWriter out, Integer value) throws IOException {out.value(String.valueOf(value));}@Overridepublic Integer read(JsonReader in) throws IOException {try {return Integer.parseInt(in.nextString());} catch (NumberFormatException e) {return -1;}}}).create();System.out.println(gson.toJson(100)); // 结果:"100"System.out.println(gson.fromJson("\"\"", Integer.class)); // 结果:-1
注意:测试空串的时候一定是 "\"\"" 而不是 "","" 代表的是没有 JSON 串,"\"\"" 才代表 JSON 里的 ""。
你说这一接管就要管两样好麻烦呀,我明明只想管序列化(或反列化)的过程的,另一个过程我并不关心,难道没有其它更简单的方法么? 当然有!就是接下来要介绍的 JsonSerializer 与 JsonDeserializer。
二、JsonSerializer 与 JsonDeserializer
JsonSerializer 和 JsonDeserializer 不用像 TypeAdapter 一样,必须要实现序列化和反序列化的过程,你可以据需要选择,如只接管序列化的过程就用 JsonSerializer ,只接管反序列化的过程就用 JsonDeserializer ,如上面的需求可以用下面的代码。
Gson gson = new GsonBuilder().registerTypeAdapter(Integer.class, new JsonDeserializer<Integer>() {@Overridepublic Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {try {return json.getAsInt();} catch (NumberFormatException e) {return -1;}}}).create();System.out.println(gson.toJson(100)); // 结果:100System.out.println(gson.fromJson("\"\"", Integer.class)); // 结果:-1
下面是所有数字都转成序列化为字符串的例子:
JsonSerializer<Number> numberJsonSerializer = new JsonSerializer<Number>() {@Overridepublic JsonElement serialize(Number src, Type typeOfSrc, JsonSerializationContext context) {return new JsonPrimitive(String.valueOf(src));}};Gson gson = new GsonBuilder().registerTypeAdapter(Integer.class, numberJsonSerializer).registerTypeAdapter(Long.class, numberJsonSerializer).registerTypeAdapter(Float.class, numberJsonSerializer).registerTypeAdapter(Double.class, numberJsonSerializer).create();System.out.println(gson.toJson(100.0f)); // 结果:"100.0"
注意:registerTypeAdapter 必须使用包装类型,所以 int.class、long.class、float.class 和 double.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 配合使用。
Gson gson = new GsonBuilder().registerTypeAdapterFactory(new TypeAdapterFactory() {@Overridepublic <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {return null;}}).create();
四、@JsonAdapter 注解
JsonAdapter 相较之前介绍的 SerializedName 、FieldNamingStrategy、Since、Until、Expose 这几个注解都是比较特殊的,其它的几个都是用在 POJO 的字段上,而这一个是用在 POJO 类上的,接收一个参数,且必须是 TypeAdpater,JsonSerializer 或 JsonDeserializer 这三个其中之一。
上面说 JsonSerializer 和 JsonDeserializer 都要配合 GsonBuilder.registerTypeAdapter 使用,但每次使用都要注册也太麻烦了,JsonAdapter 就是为了解决这个痛点的。
使用方法(以 User 为例):
@JsonAdapter(UserTypeAdapter.class) // 加在类上public class User {public User() {}public User(String name, int age) {this.name = name;this.age = age;}public User(String name, int age, String email) {this.name = name;this.age = age;this.email = email;}public String name;public int age;@SerializedName(value = "emailAddress")public String email;}
使用时不用再使用 GsonBuilder 去注册 UserTypeAdapter 了。
注意: @JsonAdapter 仅支持 TypeAdapter 或 TypeAdapterFactory(Gson 2.7开始已经支持 JsonSerializer/JsonDeserializer)
Gson gson = new Gson();User user = new User("怪盗kidou", 24, "ikidou@example.com");System.out.println(gson.toJson(user));// 结果:{"name":"怪盗kidou","age":24,"email":"ikidou@example.com"}// 为区别结果,特意把 email 字段与 @SerializedName 注解中设置的不一样
五、TypeAdapter 与 JsonSerializer、JsonDeserializer 对比
| # | TypeAdapter | JsonSerializer、JsonDeserializer |
|---|---|---|
| 引入版本 | 2.0 | 1.x |
| Stream API | 支持 | 不支持,需要提前生成 JsonElement |
| 内存占用 | 小 | 比 TypeAdapter 大 |
| 效率 | 高 | 比 TypeAdapter 低 |
| 作用范围 | 序列化 和 反序列化 | 序列化 或 反序列化 |
六、TypeAdapter 实例
注意:这里的 TypeAdapter 泛指 TypeAdapter、JsonSerializer 和 JsonDeserializer。
这里的 TypeAdapter 上面讲了一个自动将 字符串形式的数值转换成int型时可能出现 空字符串的问题,下面介绍一个其它读者的需求:
:::info
需求:
服务器返回的数据中 data 字段类型不固定,比如请求成功 data 是一个 List,不成功的时候是 String 类型,这样前端在使用泛型解析的时候,怎么去处理呢?
:::
其实这个问题的原因主要由服务器端造成的,接口设计时没有没有保证数据的一致性,正确的数据返回姿势:同一个接口任何情况下不得改变返回类型,要么就不要返,要么就返空值,如 **null**、**[]**、**{}**。
但这里还是给出解决方案:
方案一:
Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(List.class, new JsonDeserializer<List<?>>() {@Overridepublic List<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {if (json.isJsonArray()){// 这里要自己负责解析了Gson newGson = new Gson();return newGson.fromJson(json,typeOfT);}else {// 和接口类型不符,返回空 Listreturn Collections.EMPTY_LIST;}}}).create();
方案二:
Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(List.class, new JsonDeserializer<List<?>>() {@Overridepublic List<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {if (json.isJsonArray()) {JsonArray array = json.getAsJsonArray();Type itemType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];List list = new ArrayList<>();for (int i = 0; i < array.size(); i++) {JsonElement element = array.get(i);Object item = context.deserialize(element, itemType);list.add(item);}return list;} else {// 和接口类型不符,返回空 Listreturn Collections.EMPTY_LIST;}}}).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。
不服来战!
