×

学会使用 Gson @SerializedName

96
無名小子的杂货铺
2016.09.02 08:51* 字数 1381

平常使用的有关 Json 转换的库中 Gson 和 fastJson 库用的最多,今天来说说 Gson 在 SimpleNews.io 项目中的使用,对了本次使用的版本为 gson-gson-2.2.4,现在已经更新到了 2.7 版本。

主要内容

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

四、 参考

以下是写作过程中参考的资料:

Android
Web note ad 1