平常使用的有關 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、自己使用的時候儘量封裝以下,避免以後換庫導致修改地方過多;
四、 參考
以下是寫作過程中參考的資料: