Android探究之[email protected]
阿新 • • 發佈:2018-12-15
@SerializedName註解的意義
當我們使用Gson解析Json資料時都會建立一個對應實體類,有時候Json資料裡面的欄位是Java關鍵詞或者Json資料裡面的欄位太簡單,我們想在實體類中自定義欄位名,這時就可以用@SerializedName註解。
@SerializedName註解,不管是物件轉Json還是Json轉物件,欄位名稱會被替換成註解的名字。
@SerializedName這個註解解決了我們Model和Json不對應的問題,好處:
- 首先將伺服器欄位和客戶端欄位名稱區分,不用保持一一對應關係,客戶端定義的欄位不用根據服務端介面欄位改變而改變,只需要更改@SerializedName中的取值即可;
我們輸出一個Json格式的資料也可以使用@SerializedName不用為了輸出格式而影響java中駝峰命名規範;
例項
public class Test { public static void main(String[] args) { Gson gson = new Gson(); User user = new User("juneyu", "18"); String json = gson.toJson(user); System.out.println("obj->json:" + json); User user2 = gson.fromJson(json, User.class); System.out.println("json->obj:" + user2); } public static class User{ @SerializedName("Name") private String name; @SerializedName("Age") private String age; public User(String name, String age) { this.name = name; this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age='" + age + '\'' + '}'; } }
輸出為:
obj->json:{"Name":"juneyu","Age":"18"}
json->obj:User{name='juneyu', age='18'}
實現原理
檢視Gson原始碼,在ReflectiveTypeAdapterFactory類中有如下程式碼:
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()); List<String> fieldNames = getFieldNames(field); BoundField previous = null; for (int i = 0; i < fieldNames.size(); ++i) { String name = fieldNames.get(i); if (i != 0) serialize = false; // only serialize the default name BoundField boundField = createBoundField(context, field, name, TypeToken.get(fieldType), serialize, deserialize); BoundField replaced = result.put(name, boundField); if (previous == null) previous = replaced; } 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; } /** first element holds the default name */ private List<String> getFieldNames(Field f) { SerializedName annotation = f.getAnnotation(SerializedName.class); if (annotation == null) { String name = fieldNamingPolicy.translateName(f); return Collections.singletonList(name); } String serializedName = annotation.value(); String[] alternates = annotation.alternate(); if (alternates.length == 0) { return Collections.singletonList(serializedName); } List<String> fieldNames = new ArrayList<String>(alternates.length + 1); fieldNames.add(serializedName); for (String alternate : alternates) { fieldNames.add(alternate); } return fieldNames; }
在getFieldNames方法中,在獲取Field時去匹配了SerializedName註解類標示的欄位,存在的話取的是註解設定的值。
其它
情況一:多個欄位取一個
專案中只用了一個欄位來更改解析欄位名,還有一種情況,我們在開發的時候會用到,這裡舉一個不太合適的例子,例如:後臺同學給配資料,後期要廢棄其中一個欄位,但又不能影響老版本的使用,於是增加了一個欄位,取值相同。
解決:
當然我們在新版本直接將欄位改成新欄位取值就好了。
這是一種解決辦法,但是不能保證以後沒有其它欄位廢棄或者新增,這裡在介紹一個屬性alternate簡明知意,用來替換;
可以這麼寫:
@SerializedName(value = "Name", alternate = {"NameNew"})
當出現Name或者NameNew欄位時,就會主動匹配,當然如果都存在就匹配最後一個,這樣在老版本上雖然伺服器返回的是增加NameNew的資料,但是客戶端使用的是@SerializedName("Name") 來解析的,所以也不會出問題,在新版本上使用NameNew欄位,等完全替代老版本以後,就可以在伺服器中去掉原來的Name欄位,當然我這種情況是比較理想的,一般也不會說隨意更改欄位含義,但也不排除這種可能,如果有那我們自然應對就好。
注意:
1、千萬注意要解析成物件的類,和物件轉成Json的類,不要去混淆,否則會解析不成功,在Android中可以修改proguard-project.txt檔案來過濾不混淆的類;
2、需要注入到JS當中的類不能混淆;
3、另外在使用Gson和FastJson中,發現 FastJson 在某些情況下內部會出現空指標,而且資料解析有可能不正確,專案中遇到一次在某條資料下出問題,然後替換了Gson就好了,具體區別還查證;
4、自己使用的時候儘量封裝以下,避免以後換庫導致修改地方過多;