@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、自己使用的時候盡量封裝以下,避免以后換庫導致修改地方過多;
