1 背景
Android客户端解析网络请求,目前比较常用的做法是Retrofit+Gson,最小集配置的做法如下:
复制代码
new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create())
这样做可以满足绝大部分的数据解析,但是会遇到两个问题:
这里异构列表是指:列表中包含的每一项数据的 java 实体类可以不同。
本文将探讨这两个问题产生的原因及解法。
2 前置知识
1、gson 与 retrofit 的结合做了什么?
//第一步:初始一个可用的解析gson的retrofit
new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create())
//第二步:加入TypeAdapter的列表
public static GsonConverterFactory create() {
return create(new Gson());
}
//Gson()的构造函数中加入了TypeAdapter的列表
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY);
factories.add(TypeAdapters.STRING_FACTORY);
...
factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
...
factories.add(new ReflectiveTypeAdapterFactory(
constructorConstructor, fieldNamingStrategy, excluder, jsonAdapterFactory));
//第三步:当数据从网络回来后被GsonConverterFactory.java拦截:
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonResponseBodyConverter<>(gson, adapter);
}
//第四步:使用对应的TypeAdapter完成数据解析
public T convert(ResponseBody value) throws IOException {
JsonReader jsonReader = gson.newJsonReader(value.charStream());
...
try {
result = adapter.read(jsonReader);
}
复制代码
以上四个代码片段,可以看到 Retrofit+Gson 的解析,是将 Json 串用 JsonReader 读入,根据类型用对应的 TypeAdapter 完成解析。
2、我们常用的 Gson 解析方法,将一个 json 串转换成对应的 java 实体类,是怎么做到的?
将一个 json 串,转换成对应的 java 实体类常用的方法有三个:
方法一:
new Gson().fromGson()
public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
...
TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
TypeAdapter<T> typeAdapter = getAdapter(typeToken);
T object = typeAdapter.read(reader);
...
}
复制代码
方法二:
Gson gson =
new GsonBuilder().registerTypeAdapter().create();
registerTypeAdapter 需要传入 TypeAdapter, JsonSerializer 或 JsonDeserializer ,在 TypeAdapter、JsonDeserializer 中重写对应的 read 方法完成解析。此外还可以使用 registerTypeHierarchyAdapter 方法进行注册。这两个方法的区别如下:
header | registerTypeAdapter | registerTypeHierarchyAdapter |
---|
支持泛型 | 是 | 否 |
支持继承 | 否 | 是 |
方法三:
在要解析的类上添加注释 @JsonAdapter,将 TypeAdpater,TypeAdapterFactory,JsonSerializer 或 JsonDeserializer 其中之一作为传入参数。之后的解析方法同方法二。
注意:@JsonAdapter 的优先级比 GsonBuilder.registerTypeAdapter 的优先级更高。
从三种方法中可以看出,最终都是使用 TypeAdpater 或 JsonDeserializer 进行解析。
从 json 转成 Object 的数据解析,只要重写 TypeAdapter 的 read()或者 JsonDeserializer 的 deserialize()方法即可:
public abstract class TypeAdapter<T> {
public abstract T read(JsonReader in) throws IOException;
}
public interface JsonDeserializer<T> {
public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException;
}
复制代码
TypeAdapter 和 JsonDeserializer 的解析效率不同,如下表,所以优先选用 TypeAdapter。Retrofit+Gson 也是优先选用的 TypeAdapter。
TypeAdapter 和 JsonDeserializer 的比较
– | TypeAdapter | JsonSerializer、JsonDeserializer |
---|
引入版本 | 2.0 | 1.x |
Stream API | 支持 | 不支持*,需要提前生成JsonElement |
内存占用 | 小 | 比TypeAdapter大 |
效率 | 高 | 比TypeAdapter低 |
3 前服务端下发字段和客户端字段类型不兼容的处理
根据前两节的分析,Gson 利用各种 TypeAdapter 完成基本数据类型和自定义类型的数据解析。默认情况下,解析失败时,向上抛出异常,如果没有特别处理此异常,则整个解析失败。所以,我们据此,在异常发生时,跳过这个字段的解析,继续向下解析,从而完成字段类型不兼容的处理。
前文讲到,Retrofit+Gson 初始化时,添加了一系列 TypeAdapter,其中包括 ReflectiveTypeAdapterFactory.java。这个类根据反射拿到要解析的 java 实体类的类型,完成解析。read()的关键代码如下:
public T read(JsonReader in) throws IOException {
...
try {
in.beginObject();
while(in.hasNext()) {
String name = in.nextName();
ReflectiveTypeAdapterFactory.BoundField field = (ReflectiveTypeAdapterFactory.BoundField)this.boundFields.get(name);
if (field != null && field.deserialized) {
field.read(in, instance);
} else {
in.skipValue();
}
}
} catch (IllegalStateException var5) {
throw new JsonSyntaxException(var5);
} catch (IllegalAccessException var6) {
throw new AssertionError(var6);
}
in.endObject();
return instance;
}
}
复制代码
从上述代码可见,当 field.read(in, instance)发成异常时,会将异常继续向上抛出,如果没有处理这个异常,则会造成解析失败。据此我们修改代码如下:
public T read(JsonReader in) throws IOException {
...
try {
while (in.hasNext()) {
String name = in.nextName();
BoundField field = boundFields.get(name);
if (field == null || !field.deserialized) {
in.skipValue();
} else {
try {
field.read(in, instance);
} catch (Exception e) {
in.skipValue();
}
}
}
}
catch (Exception e) {
//throw new AssertionError(e);
in.skipValue();
}
in.endObject();
return instance;
}
复制代码
当 field.read(in, instance)发生异常时,跳过字段,继续向下解析。
4 异构列表解析
json 数据结构定义如下,增加"type"字段用于标识对应的的 java 实体类:
{
"list": [
{
"type": "a",
...
},
{
"type": "b",
...
},
{
"type": "c",
...
}
]
}
复制代码
抽象出 BaseCard 基类
public class BaseCard {
@SerializedName("type")
private String cardType;
}
复制代码
其他自定义卡片类型 ACard, BCard, CCard 都是 BaseCard 的子类。
public class ACard extends BaseCard {
}
复制代码
自定义 TypeAdapter,建立不同 type 和 java 实体类的映射关系,并将这种映射关系保存在 mMap 中。
protected <T extends BaseTypeT> void addSubTypeAdapter(TypeAdapterFactory factory,
Gson gson, String typeName, Class<T> subTypeClass) {
mMap.put(typeName, new SubTypeReadWriteAdapter<>(
gson.getDelegateAdapter(factory, TypeToken.get(subTypeClass))));
}
复制代码
重写 TypeAdapter 的 read()方法,根据 type 的值从 Map 中取出对应的 java 实体类的 TypeAdapter,从而完成数据解析。
定义 DemoTypeAdapterFactory,利用工厂模式 create()的回调,生成 TypeAdapter 列表。
public final <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return (TypeAdapter<T>) createTypeAdapter(gson);
}
public TypeAdapter<Card> createTypeAdapter(final Gson gson) {
final TypeAdapterFactory factory = this;
return new BaseTypeAdapter<Card>(gson, "type") {
{
addSubTypeAdapter(factory, gson, "a",
ACard.class);
...
}
}
复制代码
在 BaseCard.java 上添加注释:
@JsonAdapter(DemoTypeAdapterFactory.class)
从而实现了完成异构列表数据类型的解析。
作者介绍:
推敲(企业代号名),目前负责贝壳新网销平台的相关移动端工作。
本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。
原文链接:
https://mp.weixin.qq.com/s/Lqt55t66lE_4WTUVeqzo-A
评论