平常使用的有关 Json 转换的库中 Gson 和 fastJson 库用的最多,今天来说说 Gson 在 SimpleNews.io 项目中的使用,对了本次使用的版本为 gson-gson-2.2.4,现在已经更新到了 2.7 版本。
同步发布在 学会使用 Gson @SerializedName
主要内容
注解@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、自己使用的时候尽量封装以下,避免以后换库导致修改地方过多;
四、 参考
以下是写作过程中参考的资料: