注册

再谈Gson数据解析

从一个例子出发


开发的过程中,总会遇到各种各样有趣的问题,Gson是android序列化中比较老牌的框架了,本片是通过一个小例子出发,让我们更加理解gson序列化过程中的细节与隐藏的“小坑”,避免走入理解误区!


我们先举个例子吧,以下是一个json字符串


{
"new_latent_count": 8,
"data": {
"length": 25,
"text": "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
}
}
复制代码

通过插件,我们很容易生成以下类



data class TestData(
val `data`: Data,
val new_latent_count: Int
)

data class Data(
val length: Int,
val text: String
)
复制代码

这个时候有意思的是,假如我们把上述中的数据类TestData变成以下这个样子


data class TestData(
// 这里发生了改变,把Data类型变成Any类型
val `data`: Any,
val new_latent_count: Int
)
复制代码

此时,我们再用Gson去把上文的json数据去进行解析生成一个数据类TestData,此时请问


val fromJson = Gson().fromJson(
"{\n" +
" "new_latent_count": 8,\n" +
" "data": {\n" +
" "length": 25,\n" +
" "text": "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."\n" +
" }\n" +
"}", TestData::class.java
)

提问时间到! 这里是为true还是false呢!!fromJson这个对象真正的实现类还是Any or Any的子类
Log.e("hello","${fromJson.data is Data}")
复制代码

如果你的回答是fasle,那么恭喜你,你已经足够掌握Gson的流程了!如果你回答是true,那么就要小心了!因为Gson里面的细节,很容易让你产生迷糊!(答案是false) 可能有小伙伴会问了,我只是把TestData 里面的data从Data类型变成了Any而已,本质上应该还是Data才对呀!别急,我们进入gson的源码查看!


Gson源码


虽然gson源码解析网上已经有很多很多了,但是我们从带着问题出发,能够更加的理解深刻,我们从fromJson出发,最终fromJson会调用到以下类


public <T> T fromJson(JsonReader reader, TypeToken<T> typeOfT) throws JsonIOException, JsonSyntaxException {
boolean isEmpty = true;
boolean oldLenient = reader.isLenient();
reader.setLenient(true);
try {
reader.peek();
isEmpty = false;
这里会获取一个TypeAdapter,然后通过TypeAdapter的read方法去解析数据
TypeAdapter<T> typeAdapter = getAdapter(typeOfT);
return typeAdapter.read(reader);
} catch (EOFException e) {
/*
* For compatibility with JSON 1.5 and earlier, we return null for empty
* documents instead of throwing.
*/
if (isEmpty) {
return null;
}
throw new JsonSyntaxException(e);
} catch (IllegalStateException e) {
throw new JsonSyntaxException(e);
} catch (IOException e) {
// TODO(inder): Figure out whether it is indeed right to rethrow this as JsonSyntaxException
throw new JsonSyntaxException(e);
} catch (AssertionError e) {
throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e);
} finally {
reader.setLenient(oldLenient);
}
}
复制代码

接着,我们可以看看这个关键的getAdapter方法里面,做了什么!


public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
Objects.requireNonNull(type, "type must not be null");
TypeAdapter<?> cached = typeTokenCache.get(type);
尝试获取一遍有没有缓存,有的话直接返回已有的TypeAdapter对象
if (cached != null) {
@SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) cached;
return adapter;
}
// threadLocalAdapterResults是一个ThreadLocal对象,线程相关

Map<TypeToken<?>, TypeAdapter<?>> threadCalls = threadLocalAdapterResults.get();
boolean isInitialAdapterRequest = false;
if (threadCalls == null) {
// 没有就生成一个hashmap,保存到ThreadLocal里面
threadCalls = new HashMap<>();
threadLocalAdapterResults.set(threadCalls);
isInitialAdapterRequest = true;
} else {
// the key and value type parameters always agree
@SuppressWarnings("unchecked")
TypeAdapter<T> ongoingCall = (TypeAdapter<T>) threadCalls.get(type);
if (ongoingCall != null) {
return ongoingCall;
}
}

TypeAdapter<T> candidate = null;
try {
FutureTypeAdapter<T> call = new FutureTypeAdapter<>();
threadCalls.put(type, call);
// 通过遍历factories,查找能够解析的type的adapter
for (TypeAdapterFactory factory : factories) {
candidate = factory.create(this, type);
if (candidate != null) {
call.setDelegate(candidate);
// Replace future adapter with actual adapter
threadCalls.put(type, candidate);
break;
}
}
} finally {
if (isInitialAdapterRequest) {
threadLocalAdapterResults.remove();
}
}

.....
}
复制代码

这里算是Gson中,查找adapter的核心流程了,这里我们能够得到几个消息,首先我们想要获取的TypeAdapter(定义了解析流程),其实是存在缓存的,而且是放在一个ThreadLocal里面,这也就意味着它其实是跟线程本地存储相关的!其次,我们也看到,这个缓存是跟当次的Gson对象是强关联的,这也就意味着,只有用同一个Gson对象,才能享受到缓存的好处!这也就是为什么我们常说尽可能的复用同一个Gson的原因。


仅接着,我们会看到这个循环 TypeAdapterFactory factory : factories 它其实是在找factories中,有没有哪个factory可能进行本次的解析,而factories,会在Gson对象初始化的时候,被填充各种各样的factory


image.png


接下来,我们外层拿到了TypeAdapter,就会调用这个read方法去解析数据


public abstract T read(JsonReader in) throws IOException;
复制代码

每个在factories的fatory子类所生成的TypeAdapter们,都会实现这个方法


而我们上文中的问题解答终于来了,问题就在这里,当我们数据类型中,有一个Any的属性的时候,它是怎么被解析的呢?它会被哪个TypeAdapter所解析,就是咱们问题的关键了!


答案是:ObjectTypeAdapter


我们再看它的read方法


@Override public Object read(JsonReader in) throws IOException {
// Either List or Map
Object current;
JsonToken peeked = in.peek();
重点在这里
current = tryBeginNesting(in, peeked);
if (current == null) {
return readTerminal(in, peeked);
}
复制代码

这里就会去解析数据


private Object tryBeginNesting(JsonReader in, JsonToken peeked) throws IOException {
switch (peeked) {
case BEGIN_ARRAY:
in.beginArray();
return new ArrayList<>();
case BEGIN_OBJECT:
in.beginObject();
return new LinkedTreeMap<>();
default:
return null;
}
}
复制代码

我们惊讶的发现,当Any数据被解析的时候,其实就会走到BEGIN_OBJECT的分支,最终生成的是一个LinkedTreeMap对象!这也很好理解,当我们数据类不清晰的时候,json数据本质就是key-value的map,所以以map去接收就能保证之后的逻辑一致!(序列化操作过程中是没有虚拟机中额外的checkcase操作来保证类型一致的)


因此我们上文中的数据类


data class TestData(
// 这里发生了改变,把Data类型变成Any类型
val `data`: Any,
val new_latent_count: Int
)
复制代码

其实真正被解析成的是


data class TestData(
// 这里发生了改变,把Data类型变成Any类型
val `data`: LinkedTreeMap<泛型根据json数据定的k,v>,
val new_latent_count: Int
)
复制代码

所以问题就很简单,LinkedTreeMap的对象当然不是一个上文Data数据类对象,所以就是false啦!


总结


当然,Gson里面还有很多很多“坑”,需要我们时刻去注意,这方面的文章也有很多,我就不再炒冷饭了,希望通过这一个例子,能帮助我们去学习源码中了解更多的细节!下课!!!


作者:Pika
链接:https://juejin.cn/post/7222128282922582053
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册