學會使用 Gson @SerializedName

平常使用的有關 Json 轉換的庫中 Gson 和 fastJson 庫用的最多,今天來說說 Gson 在 SimpleNews.io 項目中的使用,對了本次使用的版本爲 gson-gson-2.2.4,現在已經更新到了 2.7 版本。

同步發佈在 學會使用 Gson @SerializedName

主要內容

註解@SerializedName 的使用
其它小技巧

一、Gson 是什麼?

Gson is a Java library that can be used to convert Java Objects into their JSON representation. It can also be used to convert a JSON string to an equivalent Java object. Gson can work with arbitrary Java objects including pre-existing objects that you do not have source-code of.

Gson 是一個 Java 庫,可用於將 Java 對象轉換爲 JSON 字符串表示。也可以被用來將一個 JSON 字符串轉換成一個等效的 Java 對象,Gson 可以處理 Java 對象包括已存在的對象。

二、我的需求

我們熟知 JSON 和 xml 都用於網絡傳輸的,由於 JSON 比 XML 要更輕量,所以被用於很多項目,我這裏也是使用的 JSON 作爲服務器和客戶端溝通的語言。

我的需求是將服務器返回的 JSON 轉換成 java 對象,原來是按部就班的一層一層使用 JSONObject 進行遍歷解析,不太人性化。

所以想使用 Gson 來簡化邏輯,由於還想使用原來的 model 類,也不想更改字段名稱,因爲 Gson 解析的時候是根據字段名來匹配的,然後就發現了註解 @SerializedName ,不管是對象轉 JSON 還是 JSON 轉對象,字段名稱會被替換成註解的名字,一下就解決了需求。

同樣我們也可以將一個對象轉成 JSON 字符串,也可以爲變量設置 @SerializedName 註解,這樣生成的 JSON 中,Key 值就是註解的值。

實例:
List<ImageBean> iamgeBeanList = gson.fromJson(response, new TypeToken<List<ImageBean>>() {}.getType());

        public class ImageBean implements Serializable {

            public static final long serialVersionUID = 1L;
            @SerializedName("title")
            public String title;

            @SerializedName("thumburl")
            public String thumbUrl;

            @SerializedName("sourceurl")
            public String imageUrl;

            @SerializedName("height")
            public int height;

            @SerializedName("width")
            public int width;
        }

我們將得到的數據 response (這裏的結果必須是一個正確格式的 JSON 數據),使用 fromJson() 方法來將 JSON 數據轉成 java 對象,我們起作用的註解就在 ImageBean model類,爲每一個變量添加 @SerializedName 註解,這樣在解析的時候就能轉換成註解標示的字段名。

思考:

@SerializedName 這個註解解決了我們 Model 和 JSON 不對應的問題,帶來的好處自然不言而喻。
1、首先將服務器字段和客戶端字段名稱區分,不用保持一一對應關係,
客戶端定義的字段不用跟這後臺接口字段改變兒改變,只需要更改@SerializedName 中的取值即可;
2、我們輸出一個 JSON 格式的數據也可以使用 @SerializedName 不用爲了輸出格式而影響 java 中駝峯命名規範;

實現原理:

嘗試着查看 Gson 源碼,粗略的跟了一下代碼,在 ReflectiveTypeAdapterFactory 類中大概找出原理,以下是 Gson 官方代碼:

      private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {
            Map<String, BoundField> result = new LinkedHashMap<String, BoundField>();
            if (raw.isInterface()) {
              return result;
            }

            Type declaredType = type.getType();
            while (raw != Object.class) {
              Field[] fields = raw.getDeclaredFields();
              for (Field field : fields) {
                boolean serialize = excludeField(field, true);
                boolean deserialize = excludeField(field, false);
                if (!serialize && !deserialize) {
                  continue;
                }
                field.setAccessible(true);
                Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
                BoundField boundField = createBoundField(context, field, getFieldName(field),
                    TypeToken.get(fieldType), serialize, deserialize);//關鍵
                BoundField previous = result.put(boundField.name, boundField);
                if (previous != null) {
                  throw new IllegalArgumentException(declaredType
                      + " declares multiple JSON fields named " + previous.name);
                }
              }
              type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
              raw = type.getRawType();
            }
            return result;
          }

        static String getFieldName(FieldNamingStrategy fieldNamingPolicy, Field f) {
            SerializedName serializedName = f.getAnnotation(SerializedName.class);
            return serializedName == null ? fieldNamingPolicy.translateName(f) : serializedName.value();
        }

在 getFieldName 方法中,能看出來在獲取 Field 時去匹配了 SerializedName 註解類標示的字段,存在的話取的是註解設定的值。

三、其它

情況一:多個字段取一個
項目中只用了一個字段來更改解析字段名,還有一種情況,我們在開發的時候會用到,這裏舉一個不太合適的例子,例如:後臺同學給配數據,後期要廢棄其中一個字段,但又不能影響老版本的使用,於是增加了一個字段,取值相同。

解決:
當然我們在新版本直接講字段改成新數據字段取值就好了。
這是一種解決辦法,但是不能保證以後沒有其它字段廢棄或者添加,這裏在介紹一個屬性 alternate 簡明知意,用來替換;

可以這麼寫:

@SerializedName(value = "thumburl", alternate = {"thumburl_new"})

當出現 thumburl 或者 thumburl_new 字段時,就會主動匹配,當然如果都存在就匹配最後一個,這樣在老版本上雖然 服務器返回的是增加 thumburl_new 的數據,但是客戶端 使用的是 @SerializedName(“thumburl”) 來解析的,所以也不會出問題,在新版本上使用 thumburl_new 字段,等完全替代老版本以後,就可以在服務器中去掉原來的 thumburl 字段,當然我這種情況是比較理想的,一般也不會說隨意更改字段含義,但也不排除這種可能,如果有那我們自然應對就好。

注意:

1、千萬注意要解析成對象的類,和對象轉成 JSON 的類,不要去混淆,否則會解析不成功,在 Android 中可以修改 proguard-project.txt 文件來過濾不混淆的類;
2、需要注入到 js 當中的類不能混淆;
3、另外在使用 Gson 和 fastJson 中,發現 fastJson 在某些情況下內部會出現空指針,而且數據解析有可能不正確,項目中遇到一次在某條數據下出問題,然後替換了 Gson 就好了,具體區別還查證;
4、自己使用的時候儘量封裝以下,避免以後換庫導致修改地方過多;

四、 參考

以下是寫作過程中參考的資料:

發佈了18 篇原創文章 · 獲贊 32 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章