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

簡介

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

重點(diǎn)

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

場景

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

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

這兩個方法最重要的地方都是,獲取一個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對象,不同的線程使用緩存來解析的時候互不影響砾隅。

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對象時初始化晴埂,添加了很多用于創(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()
    解析字符串第一個字符在json格式里的類型。
  2. getAdapter(typeToken)
    通過getAdapter(TypeToken<T> type)方法獲取TypeAdapter對象儒洛,分兩種情況:
    1. 類使用了@JsonAdapter
      看一下Gson初始化“factories”數(shù)組時的順序精耐,添加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);
      }
      
      如果對實(shí)體類使用了@JsonAdapter且指定的適配器存在那么就會返回@JsonAdapter里指定的適配器而不返回ReflectiveTypeAdapterFactory創(chuàng)建的卦停,這樣我們就可以自己接管后面的解析過程了向胡,具體用法參考后面給出的工程源碼。
    2. 沒有使用@JsonAdapter注解:
      這里要注意惊完,對于基礎(chǔ)知識不太牢固的人僵芹,可能會認(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í)體類中需要解析的字段添加一個集合里,在反序列化時進(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)通過加入自己的策略來控制字段是否要參與解析遭殉,在初始化的時候可以加入自己的策略。如果某個字段不符合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表示不解析該字段阁簸。這個注解使用時爬早,請注意看這里的判斷邏輯,不然很可能發(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 字段的名字筛严,比如:在同一個 Test 對象中的用戶電話,現(xiàn)在不同的接口返回不同的字段翅溺,比如: 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());
      

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

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

      Gson的使用者可以根據(jù)實(shí)際的需要對某個具體類型的序列化和反序列化的轉(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昼浦,那么該屬性的序列化和反序列化將由指定的適配器接管馍资。如果沒有這會從Gson初始化中查找對于的解析適配器。

  3. typeAdapter.read(reader)
    1. 創(chuàng)建實(shí)體類對象
      T instance = constructor.construct();
      
      具體怎樣創(chuàng)建的关噪,請查看源碼鸟蟹,也比較簡單,這個過程也是可以通過擴(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,但是如果是""者會拋異常導(dǎo)致崩潰来农,所以我們可以接管反序列化過程鞋真,當(dāng)出現(xiàn)異常時候返回0。
  1. registerTypeAdapter() 和registerTypeHierarchyAdapter()區(qū)別
    前者要求我們傳遞一個明確的類型沃于,也就是說它不支持繼承涩咖,而 后者 則可以支持繼承海诲。
  • 對補(bǔ)充中的第二點(diǎn)補(bǔ)充
    使用GsonBuilder()
    .registerTypeAdapter()或者GsonBuilder(...)
    .registerTypeHierarchyAdapter(...)注冊的適配器是繼承于TypeAdapter而不是JsonDeserializer,你發(fā)現(xiàn)怎么try...catch應(yīng)用都會崩潰檩互。這里看一下有什么不同特幔,找到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)出現(xiàn)異常,那么peeked = PEEKED_BUFFERED;由于try...catch循诉,所以不會崩潰横辆。
接下來獲取下一個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)系作者
  • 序言:七十年代末炫惩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子阿浓,更是在濱河造成了極大的恐慌他嚷,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芭毙,死亡現(xiàn)場離奇詭異筋蓖,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)退敦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門粘咖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人侈百,你說我怎么就攤上這事瓮下。” “怎么了钝域?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵讽坏,是天一觀的道長。 經(jīng)常有香客問我例证,道長路呜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮胀葱,結(jié)果婚禮上漠秋,老公的妹妹穿的比我還像新娘。我一直安慰自己抵屿,他們只是感情好庆锦,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著晌该,像睡著了一般肥荔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上朝群,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天燕耿,我揣著相機(jī)與錄音,去河邊找鬼姜胖。 笑死誉帅,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的右莱。 我是一名探鬼主播蚜锨,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼慢蜓!你這毒婦竟也來了亚再?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤晨抡,失蹤者是張志新(化名)和其女友劉穎氛悬,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耘柱,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡如捅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了调煎。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片镜遣。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖士袄,靈堂內(nèi)的尸體忽然破棺而出悲关,到底是詐尸還是另有隱情,我是刑警寧澤娄柳,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布坚洽,位于F島的核電站,受9級特大地震影響西土,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜江滨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一腊瑟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧敲霍,春花似錦肋乍、人聲如沸鹅颊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽堪伍。三九已至,卻和暖如春觅闽,著一層夾襖步出監(jiān)牢的瞬間帝雇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工蛉拙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留尸闸,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓孕锄,卻偏偏與公主長得像吮廉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子畸肆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容

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