引言
说起终端与网络的数据交互格式,Json肯定比Xml更通用,也更好用,可算轻量级,Json已成我们在实际开发中最常用的数据交换格式,而对于Json的解析,在Android平台上最常用的类库有Gson和FastJson两种。结合项目,本人主要使用 Gson来解析网络返回数据(反序列化操作),可以说,对于Gson的基本使用没有什么问题,但并没有对Gson更深入的认识,因此,在这里做一个知识点的整理。
名词简介
- Json:Json(JavaScript Object Notation)是一种轻量级的数据交换格式。用于数据转化传输,通用于PHP、Java、C++、C#、Python等编程语言的数据交换传输。它易于人阅读和编写,同时也易于机器解析和生成。
-
Gson:Gson是Google的一个开源库,利用序列化和反序列化技术,实现Json字符串和Java对象间的转换(原文:Gson (also known as Google Gson) is an open source Java library to serialize and deserialize Java objects to (and from) JSON)。
Gson特点
Gson作为开源的简单的序列化和反序列化Java对象的组件,尤其在Android开发中大部分开发人员都使用Gson来做序列化和反序列化组件,应验了行业一句“谷歌出品,必属精品”。下面来说说Gson的特点和作用。
Gson特点:
- 快速、高效
- 代码量少、简洁
- 面向对象
- 数据传递和解析方便
Gson作用:
- 提供简单易用的方法比如 toString() ,构造方法来转化Java为Json以及反转化
- 提供已存在的不可修改对象转化为Json以及反转化
- 提供对象的惯例表示
- 支持任意复杂对象
- 生成健壮、可读的JSON输出
总之,一句话就是:方便快捷强大。
Gson的起步
导入Gson
在build.gradle(Module:app)中加入,当前最新版本为2.8.5:
implementation 'com.google.code.gson:gson:2.8.5'
Gson对象
在进行序列化与反序列操作前,需要先实例化一个Gson 对象,获取 Gson 对象的方法有两种:
//通过构造函数来获取
Gson gson = new Gson();
或者
//通过 GsonBuilder 来获取,可以进行多项特殊配置
Gson gson = new GsonBuilder().create();
我通常使用第一种方式创建。
Gson接口
Gson最基本接口toJson()和fromJson():
gson.toJson(src) //序列化
gson.fromJson(src,type) //反序列化
Gson的用法
Gson的基本用法
基本数据类型的生成
由基本数据类型转在Json数据,一般用于组包上行数据。
Gson gson = new Gson();
String jsonNumber = gson.toJson(99); // 99
String jsonBoolean = gson.toJson(true); // true
String jsonString = gson.toJson("cchip"); //"String"
基本数据类型的解析
常用的基本数据类型解析有5个:
Gson gson = new Gson();
int i = gson.fromJson("99", int.class); //99
double d = gson.fromJson("\"99.99\"", double.class); //99.99
boolean b = gson.fromJson("true", boolean.class); // true
String str = gson.fromJson("cchip", String.class); // String
Gson的序列化与反序列化
Gson提供了 toJson() 和 fromJson() 两个方法用于转化 Model 与 Json,前者实现了序列化,后者实现了反序列化。
首先,定义一个实体类:
public class DeviceInfo implements Serializable {
private String ret;
private String rssi;
private String language;
private String ssid;
private String MAC;
//省略
}
简单对象转化
Gson gson = new Gson();
//DeviceInfo -> json
String json = gson.toJson(deviceInfo);
//json->DeviceInfo
DeviceInfo deviceInfo = gson.fromJson(json, DeviceInfo.class);
属性重命名
有时候,我们总是把事情想的太理想,服务端由于疏忽,容易引入大小写字母问题,导致解析出错,如:
理想的数据:
//反序列化
String dataJson = "{"ret":"succ","rssi":"70","language":"en","ssid":"cchip","MAC":"010203040506"}";
DeviceInfo deviceInfo = gson.fromJson(dataJson, DeviceInfo.class);
如果没有和服务器端沟通好或者是 API 改版了,接口返回的数据格式可能是这样的:
String dataJson = "{"Ret":"succ","rssi":"70","language":"en","ssid":"cchip","mac":"010203040506"}";
如果继续使用上一节介绍的方法,那无疑会解析出错。此时为了兼顾多种格式的数据,就需要使用 SerializedName 注解,根据 SerializedName 的声明来看,SerializedName 包含两个属性值,一个是字符串,一个是字符串数组,而字符串数组含有默认值:
public class DeviceInfo implements Serializable {
@SerializedName("Ret")
private String ret;
private String rssi;
private String language;
private String ssid;
private String MAC;
//省略
}
SerializedName的作用是为了在序列化或反序列化时,指导 Gson 如果将原有的属性名和其它特殊情况下的属性名联系起来。
还有个问题没解决,为了应对多种属性名不一致的情况,难道我们要声明多个 User 类吗?这显然是不现实的,所以还需要为 User 类设置多个备选属性名,这就需要用到 SerializedName 注解的另一个属性值 alternate 了。
public class DeviceInfo implements Serializable {
@SerializedName(value = "Ret", alternate = {"Ret", "Return"})
private String ret;
private String rssi;
private String language;
private String ssid;
private String MAC;
//省略
}
字段过滤
有时候并不是所有的字段都需要进行系列化和反序列化,因此需要对某些字段进行排除,有四种方法可以来实现这种需求。
基于@Expose注解
Expose 注解包含两个属性值,且均声明了默认值。Expose 的含义即为“暴露”,即用于对外暴露字段,serialize 用于指定是否进行序列化,deserialize 用于指定是否进行反序列化。如果字段不声明 Expose 注解,则意味着不进行序列化和反序列化操作,相当于两个属性值均为 false 。此外,Expose 注解需要和 GsonBuilder 构建的 Gson 对象一起使用才能生效。
Expose 注解的注解值声明情况有四种:
- @Expose(serialize = true, deserialize = true) //序列化和反序列化都生效
- @Expose(serialize = false, deserialize = true) //序列化时不生效,反序列化时生效
- @Expose(serialize = true, deserialize = false) //序列化时生效,反序列化时不生效
- @Expose(serialize = false, deserialize = false) //序列化和反序列化都不生效,和不写注解一样
例如:
public class DeviceInfo implements Serializable {
@Expose(serialize = true, deserialize = true) //序列化和反序列化都生效
private String ret;
private String rssi;
private String language;
private String ssid;
private String MAC;
//省略
}
按照如上的注解值,只有声明了 Expose 注解且 serialize 值为 true 的字段才能被序列化,只有声明了 Expose 注解且 deserialize 值为 true 的字段才能被反序列化。
基于版本
Gson 提供了 @Since 和 @Until 两个注解基于版本对字段进行过滤,@Since 和 @Until 都包含一个 Double 属性值,用于设置版本号。Since 的意思是“自……开始”,Until 的意思是“到……为止”,一样要和 GsonBuilder 配合使用。
当版本( GsonBuilder 设置的版本) 大于或等于 Since 属性值或小于 Until 属性值时字段会进行序列化和反序列化操作,而没有声明注解的字段都会加入序列化和反序列操作。
例如:
public class DeviceInfo implements Serializable {
@Since(1.4)
private String ret;
private String rssi;
private String language;
private String ssid;
private String MAC;
//省略
}
//反序列化
Gson gson = new GsonBuilder().setVersion(1.6).create();
String dataJson = "{"ret":"succ","rssi":"70","language":true,"ssid":"cchip","MAC":"010203040506"}";
DeviceInfo deviceInfo = gson.fromJson(dataJson, DeviceInfo.class);
基于访问修饰符
访问修饰符由 java.lang.reflect.Modifier 提供 int 类型的定义,而 GsonBuilder 对象的 excludeFieldsWithModifiers方法接收一个 int 类型可变参数,指定不进行序列化和反序列化操作的访问修饰符字段
这种比较少用到,先不举例了,后面再补充。
基于策略
GsonBuilder 类包含 setExclusionStrategies(ExclusionStrategy... strategies)方法用于传入不定长参数的策略方法,用于直接排除指定字段名或者指定字段类型。
这种也比较少用到,先不举例了,后面再补充。
TypeAdapter 自定义(反)序列化
TypeAdapter 是Gson自2.0(源码注释上说的是2.1)开始版本提供的一个泛型抽象类,用于接管某种类型的序列化和反序列化过程,包含两个主要方法 write(JsonWriter,T) 和 read(JsonReader),其它的方法都是final方法并最终调用这两个抽象方法。
public abstract class TypeAdapter<T> {
public abstract void write(JsonWriter out, T value) throws IOException;
public abstract T read(JsonReader in) throws IOException;
//其它final方法就不贴出来了,包括toJson、toJsonTree、fromJson、fromJsonTree和nullSafe等方法。
}
注意:TypeAdapter 以及 JsonSerializer 和 JsonDeserializer 都需要与 .registerTypeAdapter 或 .registerTypeHierarchyAdapter 配合使用,下面将不再重复说明。
之前已经说过Gson有一定的容错机制,比如将字符串 "ret" 写成 “Ret”,但如果有些情况下给你返了个空字符串怎么办?虽然这是服务器端的问题,但这里我们只是做一个示范,不改服务端的逻辑我们怎么容错。
根据我们上面介绍的,我只需注册一个 TypeAdapter 把它的序列化和反序列化过程接管就行了:
public class DeviceInfoTypeAdapter extends TypeAdapter<User> {
@Override
public void write(JsonWriter jsonWriter, DeviceInfo deviceInfo) throws IOException {
//流式序列化成对象开始
jsonWriter.beginObject();
//将Json的Key值都指定为大写字母开头
jsonWriter.name("Rssi").value(deviceInfo.getRssi());
//流式序列化结束
jsonWriter.endObject();
}
@Override
public User read(JsonReader jsonReader) throws IOException {
DeviceInfo deviceInfo = new DeviceInfo();
//流式反序列化开始
jsonReader.beginObject();
while (jsonReader.hasNext()) {
switch (jsonReader.nextName()) {
//首字母大小写均合法
case "ret":
case "Ret":
deviceInfo.setRet(jsonReader.nextString());
break;
case "rssi":
deviceInfo.setRssi(jsonReader.nextString());
break;
}
}
//流式反序列化结束
jsonReader.endObject();
return deviceInfo;
}
}
TypeAdapterFactory
TypeAdapterFactory,见名知意,用于创建 TypeAdapter 的工厂类。通过参数 TypeToken 来查找确定对应的 TypeAdapter,如果没有就返回 null 并由 Gson 默认的处理方法来进行序列化和反序列化操作,否则就由用户预定义的 TypeAdapter 来进行处理。
使用方式:与GsonBuilder.registerTypeAdapterFactory配合使用,通过对比Type,确定有没有对应的TypeAdapter,没有就返回null,有则返回(并使用)自定义的TypeAdapter。
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new TypeAdapterFactory() {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getType() == Integer.class || type.getType() == int.class) return intTypeAdapter;
return null;
}
})
JsonSerializer 和 JsonDeserializer
JsonSerializer 和JsonDeserializer 不用像TypeAdapter一样,必须要实现序列化和反序列化的过程,你可以据需要选择,如只接管序列化的过程就用 JsonSerializer ,只接管反序列化的过程就用 JsonDeserializer ,如上面的需求可以用下面的代码。
Gson gson = new GsonBuilder().registerTypeAdapter(DeviceInfo.class, new JsonDeserializer<DeviceInfo>() {
@Override
public DeviceInfo deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
JsonObject jsonObject = jsonElement.getAsJsonObject();
String ret = null;
//同时支持 ret 和 Ret 两种情况
if (jsonObject.has("Ret")) {
name = jsonObject.get("Ret").getAsString();
} else if (jsonObject.has("ret")) {
name = jsonObject.get("ret").getAsString();
}
String rssi = jsonObject.get("rssi").getAsString();
String ssid = jsonObject.get("ssid").getAsString();
return new DeviceInfo(...);
}
}).create();
Gson常用的没几个,但用处或用法却博大精深,还需看各个的悟性!
参考资料: