【Gson源码分析】- 彻底搞懂Gson解析流程

96
辶首木公
0.2 2019.06.01 12:01* 字数 2563

简介

Gson是google提供的一款Json解析框架,基本很多项目都会使用到。Gson自带的容错机制能够使解析过程更加友好,但是并不能帮助我们解决所以的容错问题,这时候可以通过向Gson注册自己的解析适配器来接管Gson的解析过程。下面将通过分析源码的方式,了解Gson内部实现原理和解析流程。带大家彻底搞懂Gson

重点

  • 扩展Gson容错机制
  • Gson注解使用
  • Gson解析流程

场景

  • 在解析Json数据的时候,由于后台返回的json数据格式的不规范,偶现解析数据崩溃,当然Gson自身就有一定的容错机制,但是,有些时候并不能到达项目的需要。比如:Gson对int数据的解析,当后台返回"123"和""时,前者由于Gson自身的容错处理能够正常解析,但是后者却会导致应用崩溃,其实我们更希望将""解析成“0”而不是应用崩溃。
  • 有时候,由于后台返回的json数据中某个字段的名字不一样,导致我们不得不在数据实体里面新加字段或者新创建一个实体类,这样不但会增加多余的代码,同时让代码逻辑变得更加混乱,后期难以维护。
  • 配置某些字段在序列化和反序列化过程中的行为。

fromJson(反序列化) and toJson(序列化)

这两个方法最重要的地方都是,获取一个TypeAdapter对象,调用read和write完成反序列化和序列化过程。完整代码查看 getAdapter(TypeToken<T> type)方法。

TypeAdapter<?> cached = typeTokenCache.get(type == null ? NULL_KEY_SURROGATE : type);
  if (cached != null) {
    return (TypeAdapter<T>) cached;
  }

从缓存获取TypeAdapter对象,存在者直接返回

FutureTypeAdapter<T> ongoingCall = (FutureTypeAdapter<T>) threadCalls.get(type);
  if (ongoingCall != null) {
    return ongoingCall;
  }

通过ThreadLocal缓存TypeAdapter对象,不同的线程使用缓存来解析的时候互不影响。

for (TypeAdapterFactory factory : factories) {
   TypeAdapter<T> candidate = factory.create(this, type);
    if (candidate != null) {
        call.setDelegate(candidate);
        typeTokenCache.put(type, candidate);
        return candidate;
    }
}

如果不存在缓存,那么从factories列表里查找,factories是在创建Gson对象时初始化,添加了很多用于创建TypeAdapter对象的TypeAdapterFactory。

  • fromJson(反序列化)
    实例:
private fun test(){
    val data = "{" +
         "\"errcode\": \"\"," +
         "\"errmsg\": \"success\"" +
        "}"
    val item = new Gson().fromJson(data, GsonItem::class.java)
}
  public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
    ...
      reader.peek();
      ...
      TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
      TypeAdapter<T> typeAdapter = getAdapter(typeToken);
      T object = typeAdapter.read(reader);
      return object;
      ...
  }
  1. reader.peek()
    解析字符串第一个字符在json格式里的类型。
  2. getAdapter(typeToken)
    通过getAdapter(TypeToken<T> type)方法获取TypeAdapter对象,分两种情况:
    1. 类使用了@JsonAdapter
      看一下Gson初始化“factories”数组时的顺序,添加JsonAdapterAnnotationTypeAdapterFactory对象在ReflectiveTypeAdapterFactory对象之前。看一下create方法:
      @SuppressWarnings("unchecked")
      @Override
      public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> targetType)     {
         Class<? super T> rawType = targetType.getRawType();
         JsonAdapter annotation = rawType.getAnnotation(JsonAdapter.class);
         if (annotation == null) {
             return null;
         }
         return (TypeAdapter<T>) getTypeAdapter(constructorConstructor, gson, targetType, annotation);
      }
      
      如果对实体类使用了@JsonAdapter且指定的适配器存在那么就会返回@JsonAdapter里指定的适配器而不返回ReflectiveTypeAdapterFactory创建的,这样我们就可以自己接管后面的解析过程了,具体用法参考后面给出的工程源码。
    2. 没有使用@JsonAdapter注解:
      这里要注意,对于基础知识不太牢固的人,可能会认为这里返回的是ObjectTypeAdapter实例,认为所以类都都继承于Object,所以GsonItem.class == Object.class为true,其实是不等的,这里返回的应该是ReflectiveTypeAdapterFactory实例,调用ReflectiveTypeAdapterFactory里的create返回内部Adapter对象。
      @Override public <T> TypeAdapter<T> create(Gson gson, final       TypeToken<T> type) {
        ...
        // constructorConstructor = new ConstructorConstructor(instanceCreators);
        ObjectConstructor<T> constructor = constructorConstructor.get(type);
        return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
      }
    
    • getBoundFields(gson, type, raw)
      将实体类中需要解析的字段添加一个集合里,在反序列化时进行赋值。

      1. 得到实体类所以的字段
        Field[] fields = raw.getDeclaredFields();
        
      2. 字段是否参与反序列化或者序列化过程
        boolean serialize = excludeField(field, true);
        boolean deserialize = excludeField(field, false);
        
        static boolean excludeField(Field f, boolean serialize, Excluder excluder) {
          return !excluder.excludeClass(f.getType(), serialize) &&     !excluder.excludeField(f, serialize);
        }
        

      3.excludeClassChecks(clazz)检查class类型是否符合序列化或者反序列化要求,这里可以自己点击去看一下。里面用到的Since和Until注解,作用于类,和作用于字段意思一样,将在下面讲解。

      1. excludeClassInStrategy(clazz, serialize)通过加入自己的策略来控制字段是否要参与解析,在初始化的时候可以加入自己的策略。如果某个字段不符合Gson解析要求,但是你觉得可以正常解析,那么就可以在自己的策略返回true。
       private boolean excludeClassInStrategy(Class<?> clazz, boolean serialize) {
       List<ExclusionStrategy> list = serialize ? serializationStrategies : deserializationStrategies;
       for (ExclusionStrategy exclusionStrategy : list) {
          if (exclusionStrategy.shouldSkipClass(clazz)) {
              return true;
          }
        }
          return false;
        }
      
      1. excluder.excludeField(f, serialize)过滤字段

        \color{blue}{注解:}@Since / @Until

        在配置了new GsonBuilder().setVersion(double v)时,@Since(double v)、@Until(double v)才起作用。这个查看源码可以得知。

        查看isValidVersion(clazz.getAnnotation(Since.class), clazz.getAnnotation(Until.class))方法,可以得出这两个注解的用法如下:
        比如:

        /**该属性自2.2+版本开始弃用*/
        @Until(2.2)
        private String sex;
        
        /**该属性自1.3+版本 开始启用*/
        @Since(1.3)
        private String name;
        
        /**该属性自1.4+版本开始弃用*/
        @Until(1.4)
        private String number;
        
      \color{blue}{注解:}@Expose

      是否将字段暴露出去,参与序列化和反序列化。需要 GsonBuilder 配合 .excludeFieldsWithoutExposeAnnotation() 方法使用,否则不起作用。

      if (requireExpose) {
          Expose annotation = field.getAnnotation(Expose.class);
          if (annotation == null || (serialize ? !annotation.serialize() : !annotation.deserialize())) {
              return true;
           }
        }
      

      返回true表示不解析该字段。这个注解使用时,请注意看这里的判断逻辑,不然很可能发现根本解析不出数据来。

      过滤策略

      List<ExclusionStrategy> list = serialize ? serializationStrategies : deserializationStrategies;
      if (!list.isEmpty()) {
      FieldAttributes fieldAttributes = new FieldAttributes(field);
      for (ExclusionStrategy exclusionStrategy : list) {
           if (exclusionStrategy.shouldSkipField(fieldAttributes)) {
              return true;
            }
          }
       }
      
    1. 获取字段类型

       Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
      
    2. 获取字段名字

      List<String> fieldNames = getFieldNames(field)
      
      \color{blue}{注解:}@SerializedName

      @SerializedName 可以用来配置 JSON 字段的名字,比如:在同一个 Test 对象中的用户电话,现在不同的接口返回不同的字段,比如: phone、user_phone、userphone,这种差异也可以用 @SerializedName .来解决。

      class Test{
          @SerializedName("user_phone")
          var userPhone :String? = null
          var sex = 0
      }
      

      在 @SerializedName 中,还有一个 alternate 字段,可以对同一个字段配置多个解析名称。

      class Test{
          @SerializedName(value = "user_phone",alternate = arrayOf("phone","userphone"))
          var userPhone :String? = null
          var sex = 0
      }
      
      \color{red}{注意:}

      一旦使用@SerializedName后,字段本身的名字不在起作用,所以需要指定@SerializedName中value的值。

    3. createBoundField(...)

      final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
      

      是否是基本数据类型

      \color{blue}{注解:}@JsonAdapter

      Gson的使用者可以根据实际的需要对某个具体类型的序列化和反序列化的转换进行控制,可放置在属性上。

       JsonAdapter annotation = field.getAnnotation(JsonAdapter.class);
       TypeAdapter<?> mapped = null;
       if (annotation != null) {
           mapped = jsonAdapterFactory.getTypeAdapter(
           constructorConstructor, context, fieldType, annotation);
       }
      

      如果实体类某属性使用了@JsonAdapter,那么该属性的序列化和反序列化将由指定的适配器接管。如果没有这会从Gson初始化中查找对于的解析适配器。

  3. typeAdapter.read(reader)
    1. 创建实体类对象
      T instance = constructor.construct();
      
      具体怎样创建的,请查看源码,也比较简单,这个过程也是可以通过扩展相关类来接管的。
    2. Json流开始的类型,并做上相应标记。
       in.beginObject();
      
    3. 读值
      String name = in.nextName();
        BoundField field = boundFields.get(name);
        if (field == null || !field.deserialized) {
          in.skipValue();
        } else {
          field.read(in, instance);
        }
      
      获取Json数据中的name,如果在 boundFields(需要反序列化的字段)没有者跳过,如果有,者读取对应值并赋值给实体类对应字段。
      1. field.read(in, instance)
        @Override void read(JsonReader reader, Object value)
        throws IOException, IllegalAccessException {
           Object fieldValue = typeAdapter.read(reader);
           if (fieldValue != null || !isPrimitive) {
               field.set(value, fieldValue);
           }
        }
        
        读取值并赋值给实体类,至于怎么读取的,可以看一下Gson初始化里面,已经添加的解析适配器。
  • toJson(序列化)
    基本流程和大部分实现都和fromJson(反序列化)相同,请自行查看源码。
  • 补充
    1. gson = new GsonBuilder().setDateFormat("yyyy-MM").create()可以设置日期类型在序列化和反序列化过程输出的格式。Gson提供了DefaultDateTypeAdapter和DateTypeAdapter来进行转换。
    2. GsonBuilder()
      .registerTypeAdapter(Int::class.java, IntDeserializerAdapter())
      . registerTypeHierarchyAdapter(String::class.java, NumberTypeAdapter())
      .create()
      .fromJson<GsonItem>(jsonStr,GsonItem::class.java)
      注册自己的解析适配器,代替Gson自带的。
      override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Int 
      = try {
      json!!.asInt
      } catch (e: NumberFormatException) {
          0
      }
      
      Gson能够把类似"123"解析成Int,但是如果是""者会抛异常导致崩溃,所以我们可以接管反序列化过程,当出现异常时候返回0。
  1. registerTypeAdapter() 和registerTypeHierarchyAdapter()区别
    前者要求我们传递一个明确的类型,也就是说它不支持继承,而 后者 则可以支持继承。
  • 对补充中的第二点补充
    使用GsonBuilder()
    .registerTypeAdapter()或者GsonBuilder(...)
    .registerTypeHierarchyAdapter(...)注册的适配器是继承于TypeAdapter而不是JsonDeserializer,你发现怎么try...catch应用都会崩溃。这里看一下有什么不同,找到TreeTypeAdapter类的read方法,至于为什么是这个类,请按照这篇文章逻辑梳理一遍。
 @Override public T read(JsonReader in) throws IOException {
   if (deserializer == null) {
     return delegate().read(in);
   }
   JsonElement value = Streams.parse(in);
   if (value.isJsonNull()) {
     return null;
   }
   return deserializer.deserialize(value, typeToken.getType(), context);
 }

如果deserializer不等于null,这反序列化过程由继承于JsonDeserializer的类接管。那么看一下为什么继承TypeAdapter会有问题,定位到自定义的TypeAdapter的read方法

override fun read(i: JsonReader): Int? {
     if (i.peek() == JsonToken.NULL) {
         i.nextNull()
         return 0
     }
    try {
         return i.nextInt()
    } catch (e: Exception) {
            return 0
   }
}

看一下i.nextInt()

  public int nextInt() throws IOException {
    ...
    try {
        result = Integer.parseInt(peekedString);
        peeked = PEEKED_NONE;
        pathIndices[stackSize - 1]++;
        return result;
      } catch (NumberFormatException ignored) {
      }
    } else {
      throw new IllegalStateException("Expected an int but was " + peek() + locationString());
    }
        ...
    peeked = PEEKED_BUFFERED;
    ...

如果 Integer.parseInt(peekedString)出现异常,那么peeked = PEEKED_BUFFERED;由于try...catch,所以不会崩溃。
接下来获取下一个json数据中的name,定位到ReflectiveTypeAdapterFactory中的read方法里的String name = in.nextName()方法,当peeked = PEEKED_BUFFERED抛出异常,导致程序崩溃,解决办法就是自己写解析流程而不是简单的try...catch。

更多用法请查看下面工程代码
\color{blue}{完整代码}SimpleGson

Android