Android探究之Gson@SerializedName

@SerializedName註解的意義

當咱們使用Gson解析Json數據時都會建立一個對應實體類,有時候Json數據裏面的字段是Java關鍵詞或者Json數據裏面的字段太簡單,咱們想在實體類中自定義字段名,這時就能夠用@SerializedName註解。java

@SerializedName註解,無論是對象轉Json仍是Json轉對象,字段名稱會被替換成註解的名字。json

@SerializedName這個註解解決了咱們Model和Json不對應的問題,好處:服務器

  1. 首先將服務器字段和客戶端字段名稱區分,不用保持一一對應關係,客戶端定義的字段不用根據服務端接口字段改變而改變,只須要更改@SerializedName中的取值便可;
  2. 咱們輸出一個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 + '\'' +
                    '}';
        }
    }

輸出爲:ide

obj->json:{"Name":"juneyu","Age":"18"}
json->obj:User{name='juneyu', age='18'}

實現原理

查看Gson源碼,在ReflectiveTypeAdapterFactory類中有以下代碼:this

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註解類標示的字段,存在的話取的是註解設定的值。指針

其它

狀況一:多個字段取一個code

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

解決:接口

固然咱們在新版本直接將字段改爲新字段取值就行了。
這是一種解決辦法,可是不能保證之後沒有其它字段廢棄或者添加,這裏在介紹一個屬性alternate簡明知意,用來替換;
能夠這麼寫:ip

@SerializedName(value = "Name", alternate = {"NameNew"})

當出現Name或者NameNew字段時,就會主動匹配,固然若是都存在就匹配最後一個,這樣在老版本上雖然服務器返回的是增長NameNew的數據,可是客戶端使用的是@SerializedName("Name") 來解析的,因此也不會出問題,在新版本上使用NameNew字段,等徹底替代老版本之後,就能夠在服務器中去掉原來的Name字段,固然我這種狀況是比較理想的,通常也不會說隨意更改字段含義,但也不排除這種可能,若是有那咱們天然應對就好。

注意:
一、千萬注意要解析成對象的類,和對象轉成Json的類,不要去混淆,不然會解析不成功,在Android中能夠修改proguard-project.txt文件來過濾不混淆的類;

二、須要注入到JS當中的類不能混淆;

三、另外在使用Gson和FastJson中,發現 FastJson 在某些狀況下內部會出現空指針,並且數據解析有可能不正確,項目中遇到一次在某條數據下出問題,而後替換了Gson就行了,具體區別還查證;

四、本身使用的時候儘可能封裝如下,避免之後換庫致使修改地方過多;

相關文章
相關標籤/搜索