美文网首页javaAndroid
Android探究之Gson@SerializedName

Android探究之Gson@SerializedName

作者: yujunjun | 来源:发表于2018-12-14 22:58 被阅读0次

    @SerializedName注解的意义


    当我们使用Gson解析Json数据时都会创建一个对应实体类,有时候Json数据里面的字段是Java关键词或者Json数据里面的字段太简单,我们想在实体类中自定义字段名,这时就可以用@SerializedName注解。

    @SerializedName注解,不管是对象转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 + '\'' +
                        '}';
            }
        }
    

    输出为:

    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、自己使用的时候尽量封装以下,避免以后换库导致修改地方过多;

    相关文章

      网友评论

        本文标题:Android探究之Gson@SerializedName

        本文链接:https://www.haomeiwen.com/subject/iennhqtx.html