最近项目中遇到了两个 Json 解析的坑,一个是后端没有按照约定返回对应类型的字段,文档说明写了 int ,但是它直接返回了 String 类型;另外一个数据请求成功后, data 是一个 Object 类型,但是如果数据请求失败,后端直接返回了 List
因为这两个坑,后端周末紧急加班去发版本, 不过就勾起了我对 Gson 容错的方案思考
Gson 源码的分析
要解决业务方的需求,客户端这边想法子做一定的 Json 容错,那么就必须去了解一下 Gson 的源码了
先看下 Gson 的日常用法:
Gson gson = new Gson();
//反序列化得到数据bean
DataBean data = gson.fromJson(jsonString,DataBean.class);
Gson 是如何将 Json 反序列化的?答案是通过 TypeAdapter
关于
TypeAdapter,可以看一下这篇文章:你真的会用Gson吗? 整个系列看完,Gson的基本用法就都掌握了
Gson 内部实现了大量的 TypeAdapter ,在其构造方法里面,就将全部的 TypeAdapter 存储起来:
Gson(final Excluder excluder, final FieldNamingStrategy fieldNamingStrategy,
final Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
boolean prettyPrinting, boolean lenient, boolean serializeSpecialFloatingPointValues,
LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle,
int timeStyle, List<TypeAdapterFactory> builderFactories,
List<TypeAdapterFactory> builderHierarchyFactories,
List<TypeAdapterFactory> factoriesToBeAdded) {
//省去部分代码
List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();
// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY);
// the excluder must precede all adapters that handle user-defined types
factories.add(excluder);
//重点:添加自定义的 TypeAdapter
factories.addAll(factoriesToBeAdded);
// type adapters for basic platform types
factories.add(TypeAdapters.STRING_FACTORY);
factories.add(TypeAdapters.INTEGER_FACTORY);
factories.add(TypeAdapters.BOOLEAN_FACTORY);
factories.add(TypeAdapters.BYTE_FACTORY);
factories.add(TypeAdapters.SHORT_FACTORY);
TypeAdapter<Number> longAdapter = longAdapter(longSerializationPolicy);
factories.add(TypeAdapters.newFactory(long.class, Long.class, longAdapter));
factories.add(TypeAdapters.newFactory(double.class, Double.class,
doubleAdapter(serializeSpecialFloatingPointValues)));
factories.add(TypeAdapters.newFactory(float.class, Float.class,
floatAdapter(serializeSpecialFloatingPointValues)));
factories.add(TypeAdapters.NUMBER_FACTORY);
factories.add(TypeAdapters.ATOMIC_INTEGER_FACTORY);
factories.add(TypeAdapters.ATOMIC_BOOLEAN_FACTORY);
factories.add(TypeAdapters.newFactory(AtomicLong.class, atomicLongAdapter(longAdapter)));
factories.add(TypeAdapters.newFactory(AtomicLongArray.class, atomicLongArrayAdapter(longAdapter)));
factories.add(TypeAdapters.ATOMIC_INTEGER_ARRAY_FACTORY);
factories.add(TypeAdapters.CHARACTER_FACTORY);
factories.add(TypeAdapters.STRING_BUILDER_FACTORY);
factories.add(TypeAdapters.STRING_BUFFER_FACTORY);
factories.add(TypeAdapters.newFactory(BigDecimal.class, TypeAdapters.BIG_DECIMAL));
factories.add(TypeAdapters.newFactory(BigInteger.class, TypeAdapters.BIG_INTEGER));
factories.add(TypeAdapters.URL_FACTORY);
factories.add(TypeAdapters.URI_FACTORY);
factories.add(TypeAdapters.UUID_FACTORY);
factories.add(TypeAdapters.CURRENCY_FACTORY);
factories.add(TypeAdapters.LOCALE_FACTORY);
factories.add(TypeAdapters.INET_ADDRESS_FACTORY);
factories.add(TypeAdapters.BIT_SET_FACTORY);
factories.add(DateTypeAdapter.FACTORY);
factories.add(TypeAdapters.CALENDAR_FACTORY);
factories.add(TimeTypeAdapter.FACTORY);
factories.add(SqlDateTypeAdapter.FACTORY);
factories.add(TypeAdapters.TIMESTAMP_FACTORY);
factories.add(ArrayTypeAdapter.FACTORY);
factories.add(TypeAdapters.CLASS_FACTORY);
// 重点:CollectionTypeAdapterFactory,用于处理集合的
factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));
this.jsonAdapterFactory = new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor);
factories.add(jsonAdapterFactory);
factories.add(TypeAdapters.ENUM_FACTORY);
// 重点:ReflectiveTypeAdapterFactor,用于反射生产数据bean实例
factories.add(new ReflectiveTypeAdapterFactory(
constructorConstructor, fieldNamingStrategy, excluder, jsonAdapterFactory));
//重点:unmodifiableList,一个不可修改的List
this.factories = Collections.unmodifiableList(factories);
}
那么 Gson 是怎么知道应该使用哪一个 TypeAdapter 呢?Gson 里面有一个 getAdapter 方法,通过参数 type 找到对应的 adapter:
public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
//省去部分代码
try {
FutureTypeAdapter<T> call = new FutureTypeAdapter<T>();
threadCalls.put(type, call);
//这里遍历去找对应 type 的 adapter
for (TypeAdapterFactory factory : factories) {
TypeAdapter<T> candidate = factory.create(this, type);
if (candidate != null) {
call.setDelegate(candidate);
typeTokenCache.put(type, candidate);
//找到了就立即返回
return candidate;
}
}
//省去部分代码
}
还记得上面的 TypeAdapter 的添加顺序吗?

那么我们能否这么做?参考 Gson 里面的 TypeAdapter ,添加我们需要的容错机制,作为自定义 TypeAdapter 添加到 Gson 里面?
自定义容错的 TypeAdapter
先来看一下 Gson 源码里的内置的一个 TypeAdapter ,比分说用于解析 int 和 Integer 的:
public static final TypeAdapterFactory INTEGER_FACTORY
//用于解析 int 和 Integer
= newFactory(int.class, Integer.class, INTEGER);
public static final TypeAdapter<Number> INTEGER = new TypeAdapter<Number>() {
@Override
public Number read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
try {
return in.nextInt();
} catch (NumberFormatException e) {
//这里抛出了json解析异常
throw new JsonSyntaxException(e);
}
}
@Override
public void write(JsonWriter out, Number value) throws IOException {
out.value(value);
}
};
现在来参考上面的代码,写一个针对解析 int 和 Integer 解析异常的 TypeAdapter :
//使用Integer可以让getAdapter更加精准找到该adapter
public class IntegerTypeAdapter extends TypeAdapter<Integer> {
@Override
public Integer read(JsonReader in) throws IOException {
//获取一个JsonToken而不会消耗它,JsonToken为这次读取的数据基本类型枚举
JsonToken peek = in.peek();
if (peek == JsonToken.NULL) {
//next方法为读取当前数据并将指针指向下个数据
in.nextNull();
return null;
}
if (peek == JsonToken.NUMBER) {
try {
//next方法为读取当前数据并将指针指向下个数据
return in.nextInt();
} catch (NumberFormatException e) {
//JsonToken.NUMBER 对应int、long和double这3个类型
//有可能会抛出这个异常
return 0;
}
}
if (peek == JsonToken.STRING) {
String result = in.nextString();
if (TextUtils.isEmpty(result)) {
return 0;
}
try {
//next方法为读取当前数据并将指针指向下个数据
return Integer.parseInt(result);
} catch (NumberFormatException e) {
//这里可以添加埋点上报或者将异常打印出来
return 0;
}
}
//设置为跳过读取解析当前数据并返回0
in.skipValue();
return 0;
}
}
@Override
public void write(JsonWriter out, Integer value) throws IOException {
out.value(value);
}
}
ok,现在来测试一下这个新的 TypeAdapter :
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(TypeAdapters.newFactory(int.class, Integer.class
, new IntegerTypeAdapter()))
.create();
Log.d("json1",gson.fromJson("\"\"",int.class) + "");//空字符串
Log.d("json2",gson.fromJson("10.1f",Integer.class) + "");//float类型
Log.d("json3",gson.fromJson("202101311934L",int.class) + "");//long类型
Log.d("json4",gson.fromJson("\"NULL\"",Integer.class) + "");//奇怪的字符串
看一下运行结果:

可以看到, Gson 运行没有报错并返回了默认值 0 ,这说明了思路是对的,那么现在要解决第二个问题了
data 是一个 Object 类型,但是如果数据请求失败,后端直接返回了 List
要解决这个问题,那么就需要先找到 Gson 是怎么解析 Object 类型的数据 Bean 了,我们都知道 Gson 将 json 反序列化为 数据Bean 是通过反射的,那么 Gson 肯定有一个基于反射的 TypeAdapter ,对 Gson 的构造方法还有印象的话,应该还记得 Gson 构造方法里面有一个 ReflectiveTypeAdapterFactory
ReflectiveTypeAdapterFactory
在 Gson 添加 TypeAdapter 时,ReflectiveTypeAdapterFactory 是最后才添加进去的,因为在基本数据类型都不匹配的情况下,我们可以将其视为一个 数据Bean,那么就轮到 ReflectiveTypeAdapterFactory 来处理了,因为它可以通过反射来创建类的实例以及给类的实例赋值,这一点也清楚写在 ReflectiveTypeAdapterFactory 的注释里面

我们来简单分析一下它是怎么做的吧,ReflectiveTypeAdapterFactory 是一个工厂,它创建的 TypeAdapter 是 ReflectiveTypeAdapterFactory 里面的一个内部类:
public class ReflectiveTypeAdapter<T> extends TypeAdapter<T> {
//省去部分代码
@Override
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
Class<? super T> raw = type.getRawType();
if (!Object.class.isAssignableFrom(raw)) {
return null; // it's a primitive!
}
ObjectConstructor<T> constructor = constructorConstructor.get(type);
//这里创建真正的 TypeAdapter
return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
}
//省去部分代码
//ReflectiveTypeAdapterFactory的内部类,用于解析json并使用反射构建类
public static final class Adapter<T> extends TypeAdapter<T> {
private final ObjectConstructor<T> constructor;
private final Map<String, BoundField> boundFields;
Adapter(ObjectConstructor<T> constructor, Map<String, BoundField> boundFields) {
this.constructor = constructor;
this.boundFields = boundFields;
}
@Override
public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
T instance = constructor.construct();
try {
//这里开始使用 Object 进行反序列化了
//如果此时后端返回的是 List 类型数据,那么就会抛出异常并且App crash
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
BoundField field = boundFields.get(name);
if (field == null || !field.deserialized) {
in.skipValue();
} else {
field.read(in, instance);
}
}
} catch (IllegalStateException e) {
throw new JsonSyntaxException(e);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
in.endObject();
return instance;
}
@Override
public void write(JsonWriter out, T value) throws IOException {
//这里处理空数据
if (value == null) {
out.nullValue();
return;
}
out.beginObject();
try {
for (BoundField boundField : boundFields.values()) {
if (boundField.writeField(value)) {
out.name(boundField.name);
boundField.write(out, value);
}
}
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
out.endObject();
}
}
}
看到这里,其实就明白了,解决方案很简单,在 in.beginObject(); 这行代码执行之前,判断一下输入的 json 类型即可:
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
//======插入容错的代码的分割线==================
if (in.peek() != JsonToken.BEGIN_OBJECT) {
//当前不是 Object 类型数据,跳过解析该数据
in.skipValue();
return null;
}
//======插入容错的代码的分割线==================
T instance = constructor.construct();
这样子就可以解决问题了,不过 Gson 源码没有做这个判断,那应该怎么做呢?其实有两个法子
一个是使用代码插桩,在编程过程中在 ReflectiveTypeAdapterFactory 里面插入上面这一段代码
可以参考这个:GsonPlugin
另一个就不怎么优雅,也比较危险,直接将 ReflectiveTypeAdapterFactory 相关代码拷贝出来,然后加上上面那一段代码
再将我们修改后的 ReflectiveTypeAdapterFactory 插入在 Gson 缓存的 TypeAdapter 列表(这是一个不可修改的List,就算反射获取到该对象也无法进行 add 或者 remove,因此只能加入到自定义 TypeAdapter 里面)
同时要想法子不影响其他类型的反序列化,这就是为什么说这种做法很危险
因为这么做,很容易就导致整个 Gson 的 反射序列化实例对象出问题 !
因此,最好使用 代码插桩 的方法来完成 Gson 容错处理

本文探讨了在遇到Gson反序列化时遇到的问题,包括后端返回类型不一致和数据请求失败时返回List。通过分析Gson源码,了解其工作原理,并提出自定义TypeAdapter实现容错机制,特别介绍了如何处理int与Integer转换异常以及Object与List之间的转换问题。同时,提出了两种解决方案:代码插桩和修改ReflectiveTypeAdapterFactory,建议使用代码插桩以避免潜在风险。
4823

被折叠的 条评论
为什么被折叠?



