再谈Gson数据解析

1,974 阅读3分钟

从一个例子出发

开发的过程中,总会遇到各种各样有趣的问题,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里面还有很多很多“坑”,需要我们时刻去注意,这方面的文章也有很多,我就不再炒冷饭了,希望通过这一个例子,能帮助我们去学习源码中了解更多的细节!下课!!!

本文正在参加「金石计划」