学会使用 Gson @SerializedName

作者: 無名小子的杂货铺 | 来源:发表于2016-09-02 08:51 被阅读5400次

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

    四、 参考

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

    相关文章

      网友评论

      本文标题:学会使用 Gson @SerializedName

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