【Gson源碼分析】- 徹底搞懂Gson解析流程

簡介

Gson是google提供的一款Json解析框架,基本很多項(xiàng)目都會(huì)使用到。Gson自帶的容錯(cuò)機(jī)制能夠使解析過程更加友好,但是并不能幫助我們解決所以的容錯(cuò)問題,這時(shí)候可以通過向Gson注冊自己的解析適配器來接管Gson的解析過程。下面將通過分析源碼的方式,了解Gson內(nèi)部實(shí)現(xiàn)原理和解析流程。帶大家徹底搞懂Gson

重點(diǎn)

  • 擴(kuò)展Gson容錯(cuò)機(jī)制
  • Gson注解使用
  • Gson解析流程

場景

  • 在解析Json數(shù)據(jù)的時(shí)候,由于后臺(tái)返回的json數(shù)據(jù)格式的不規(guī)范,偶現(xiàn)解析數(shù)據(jù)崩潰,當(dāng)然Gson自身就有一定的容錯(cuò)機(jī)制,但是,有些時(shí)候并不能到達(dá)項(xiàng)目的需要。比如:Gson對int數(shù)據(jù)的解析,當(dāng)后臺(tái)返回"123"和""時(shí),前者由于Gson自身的容錯(cuò)處理能夠正常解析,但是后者卻會(huì)導(dǎo)致應(yīng)用崩潰,其實(shí)我們更希望將""解析成“0”而不是應(yīng)用崩潰。
  • 有時(shí)候,由于后臺(tái)返回的json數(shù)據(jù)中某個(gè)字段的名字不一樣,導(dǎo)致我們不得不在數(shù)據(jù)實(shí)體里面新加字段或者新創(chuàng)建一個(gè)實(shí)體類,這樣不但會(huì)增加多余的代碼,同時(shí)讓代碼邏輯變得更加混亂,后期難以維護(hù)。
  • 配置某些字段在序列化和反序列化過程中的行為。

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

這兩個(gè)方法最重要的地方都是,獲取一個(gè)TypeAdapter對象,調(diào)用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對象,不同的線程使用緩存來解析的時(shí)候互不影響。

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是在創(chuàng)建Gson對象時(shí)初始化,添加了很多用于創(chuàng)建TypeAdapter對象的TypeAdapterFactory。

  • fromJson(反序列化)
    實(shí)例:
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()
    解析字符串第一個(gè)字符在json格式里的類型。
  2. getAdapter(typeToken)
    通過getAdapter(TypeToken<T> type)方法獲取TypeAdapter對象,分兩種情況:
    1. 類使用了@JsonAdapter
      看一下Gson初始化“factories”數(shù)組時(shí)的順序,添加JsonAdapterAnnotationTypeAdapterFactory對象在ReflectiveTypeAdapterFactory對象之前??匆幌耤reate方法:
      @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);
      }
      
      如果對實(shí)體類使用了@JsonAdapter且指定的適配器存在那么就會(huì)返回@JsonAdapter里指定的適配器而不返回ReflectiveTypeAdapterFactory創(chuàng)建的,這樣我們就可以自己接管后面的解析過程了,具體用法參考后面給出的工程源碼。
    2. 沒有使用@JsonAdapter注解:
      這里要注意,對于基礎(chǔ)知識(shí)不太牢固的人,可能會(huì)認(rèn)為這里返回的是ObjectTypeAdapter實(shí)例,認(rèn)為所以類都都繼承于Object,所以GsonItem.class == Object.class為true,其實(shí)是不等的,這里返回的應(yīng)該是ReflectiveTypeAdapterFactory實(shí)例,調(diào)用ReflectiveTypeAdapterFactory里的create返回內(nèi)部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)
      將實(shí)體類中需要解析的字段添加一個(gè)集合里,在反序列化時(shí)進(jìn)行賦值。

      1. 得到實(shí)體類所以的字段
        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類型是否符合序列化或者反序列化要求,這里可以自己點(diǎn)擊去看一下。里面用到的Since和Until注解,作用于類,和作用于字段意思一樣,將在下面講解。

      1. excludeClassInStrategy(clazz, serialize)通過加入自己的策略來控制字段是否要參與解析,在初始化的時(shí)候可以加入自己的策略。如果某個(gè)字段不符合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)時(shí),@Since(double v)、@Until(double v)才起作用。這個(gè)查看源碼可以得知。

        查看isValidVersion(clazz.getAnnotation(Since.class), clazz.getAnnotation(Until.class))方法,可以得出這兩個(gè)注解的用法如下:
        比如:

        /**該屬性自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表示不解析該字段。這個(gè)注解使用時(shí),請注意看這里的判斷邏輯,不然很可能發(fā)現(xiàn)根本解析不出數(shù)據(jù)來。

      過濾策略

      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 字段的名字,比如:在同一個(gè) Test 對象中的用戶電話,現(xiàn)在不同的接口返回不同的字段,比如: phone、user_phone、userphone,這種差異也可以用 @SerializedName .來解決。

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

      在 @SerializedName 中,還有一個(gè) alternate 字段,可以對同一個(gè)字段配置多個(gè)解析名稱。

      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());
      

      是否是基本數(shù)據(jù)類型

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

      Gson的使用者可以根據(jù)實(shí)際的需要對某個(gè)具體類型的序列化和反序列化的轉(zhuǎn)換進(jìn)行控制,可放置在屬性上。

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

      如果實(shí)體類某屬性使用了@JsonAdapter,那么該屬性的序列化和反序列化將由指定的適配器接管。如果沒有這會(huì)從Gson初始化中查找對于的解析適配器。

  3. typeAdapter.read(reader)
    1. 創(chuàng)建實(shí)體類對象
      T instance = constructor.construct();
      
      具體怎樣創(chuàng)建的,請查看源碼,也比較簡單,這個(gè)過程也是可以通過擴(kuò)展相關(guān)類來接管的。
    2. Json流開始的類型,并做上相應(yīng)標(biāo)記。
       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數(shù)據(jù)中的name,如果在 boundFields(需要反序列化的字段)沒有者跳過,如果有,者讀取對應(yīng)值并賦值給實(shí)體類對應(yīng)字段。
      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);
           }
        }
        
        讀取值并賦值給實(shí)體類,至于怎么讀取的,可以看一下Gson初始化里面,已經(jīng)添加的解析適配器。
  • toJson(序列化)
    基本流程和大部分實(shí)現(xiàn)都和fromJson(反序列化)相同,請自行查看源碼。
  • 補(bǔ)充
    1. gson = new GsonBuilder().setDateFormat("yyyy-MM").create()可以設(shè)置日期類型在序列化和反序列化過程輸出的格式。Gson提供了DefaultDateTypeAdapter和DateTypeAdapter來進(jìn)行轉(zhuǎn)換。
    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,但是如果是""者會(huì)拋異常導(dǎo)致崩潰,所以我們可以接管反序列化過程,當(dāng)出現(xiàn)異常時(shí)候返回0。
  1. registerTypeAdapter() 和registerTypeHierarchyAdapter()區(qū)別
    前者要求我們傳遞一個(gè)明確的類型,也就是說它不支持繼承,而 后者 則可以支持繼承。
  • 對補(bǔ)充中的第二點(diǎn)補(bǔ)充
    使用GsonBuilder()
    .registerTypeAdapter()或者GsonBuilder(...)
    .registerTypeHierarchyAdapter(...)注冊的適配器是繼承于TypeAdapter而不是JsonDeserializer,你發(fā)現(xiàn)怎么try...catch應(yīng)用都會(huì)崩潰。這里看一下有什么不同,找到TreeTypeAdapter類的read方法,至于為什么是這個(gè)類,請按照這篇文章邏輯梳理一遍。
 @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會(huì)有問題,定位到自定義的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)出現(xiàn)異常,那么peeked = PEEKED_BUFFERED;由于try...catch,所以不會(huì)崩潰。
接下來獲取下一個(gè)json數(shù)據(jù)中的name,定位到ReflectiveTypeAdapterFactory中的read方法里的String name = in.nextName()方法,當(dāng)peeked = PEEKED_BUFFERED拋出異常,導(dǎo)致程序崩潰,解決辦法就是自己寫解析流程而不是簡單的try...catch。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.概述2.Gson的目標(biāo)3.Gson的性能和擴(kuò)展性4.Gson的使用者5.如何使用Gson 通過Maven來使用...
    人失格閱讀 14,547評論 2 18
  • 概況 Gson是一個(gè)Java庫,它可以用來把Java對象轉(zhuǎn)換為JSON表達(dá)式,也可以反過來把JSON字符串轉(zhuǎn)換成與...
    木豚閱讀 6,993評論 0 2
  • 泛型: https://juejin.im/post/5b614848e51d45355d51f792#headi...
    RexHuang閱讀 7,060評論 0 0
  • 牽掛是一種說不出的痛,痛中有樂。 更加是一種改不了的癡,癡中有甜。 **人生如夢** **夢中,...
    把幸福搞丟了閱讀 332評論 0 0
  • 昨天早晨,朋友說要去做直腸鏡,問要不要一起去。我沒加思索就拒絕了,并不是說我對自己的腸子多有信心,事實(shí)上,我連做直...
    銀子姐閱讀 1,287評論 5 6

友情鏈接更多精彩內(nèi)容