在上篇文章里已经讲解过Gson的基本操作,这应该算是一个简易的基础入门吧。接下来,我们要尝试着如何自定义的去实现我们需要的序列化与反序列化操作。在讲解技术之前,先谈谈我们为什么需要去实现自定义的序列化与反序列化。说起这个为什么,其实说白了就一句话“不是所有前端想要的数据,你的后端都愿意给”,当然,你有个非常靠谱又非常乐于助人的后端除外,或者说,你是一个还算有姿色女程序员。如果这两者你都不是,我觉得,学会自定义序列化与反序列化是真的很有必要的。
在移动开发中,后端给的数据基本上如下格式
public class HttpResponse<T> {
int code;
String message;
T data;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
code返回码,message返回说明,data返回的数据,这里用了一个泛型,因为每个接口返回的数据格式是不同的。前端这样封装返回数据对象感觉是合理可靠的。按照正常的业务逻辑,如果接口访问成功,data返回正确的数据,如果接口访问失败(参数错误或者其他原因)data返回空值null,这种“友好的情况”下,Gson框架是可以成功的帮我们解析数据的。但是,但是,但是,这种情况的概率是非常非常低的,即使你一再的跟你的后端强调一定要这样做。更多的情况下,在接口访问成功的情况下,后端给你返回的数据类型是A,在接口访问失败的情况下,后端返回的数据类型是B。这种情况就问你气不气?改是不可能改的,这辈子都不可能改的,前端自己适配去吧。在产业链上游的都是大爷。我们模拟一下这种情况吧。
假设前端需要用到用户数据,调用接口向后端请求,正常情况下返回用户对象如下如下
public class UserBean {
String name;
int age;
boolean flage;
public UserBean(String name, int age, boolean flage) {
this.name = name;
this.age = age;
this.flage = flage;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isFlage() {
return flage;
}
public void setFlage(boolean flage) {
this.flage = flage;
}
@Override
public String toString() {
return "UserBean{" +
"name='" + name + '\'' +
", age=" + age +
", flage=" + flage +
'}';
}
}
定义两个方法,模拟接口访问成功与失败情况下,后端返回的数据对象
public @NonNull
HttpResponse getSuccessResponse() {
HttpResponse<UserBean> response = new HttpResponse<>();
response.setCode(200);
response.setMessage("成功");
response.setData(new UserBean("会写代码的哈市器", 22, true));
return response;
}
public @NonNull
HttpResponse getFailedResponse() {
HttpResponse<String> response = new HttpResponse<>();
response.setCode(100);
response.setMessage("失败");
response.setData("老子就是不想给你返回空值,你能把老子怎么样?");
return response;
}
从上面可以看出在接口访问成功的情况下是,后端返回的数据对象是UserBean,但是在接口访问失败的情况下,返回的数据是String。最外层依然是HttpResponse封装。这种情况很常见,我从事的每一家公司都发生过这种情况。在Http接口定义的时候(Retrofit),我们在HttpResponse<UserBean> 返回类型的泛型定义上就已经定义了UserBean,如果我们接口访问失败的情况下,Gson直接抛出解析异常。这种情况是不合理的。为什么不合理?举个例子,假设因为我们传给后端的参数错误而直接导致接口访问失败,无法获取正确数据而引发程序异常,从异常的分类来说,这是属于服务端返回异常,一般前端都要做相应的处理,比如说提示用户参数错误什么的。但是因为服务端返回的数据不合乎规范,导致数据解析失败,程序异常,这种异常是属于应用异常,不应该发生的。
下面模拟正常的接口数据解析操作。
// 模拟正常解析
public void deserializer(@NonNull String json) {
try {
Gson gson = new Gson();
Type type = new TypeToken<HttpResponse<UserBean>>() {
}.getType();
HttpResponse response = gson.fromJson(json, type);
Toast.makeText(this, "解析成功: " + response.getData().toString(), Toast.LENGTH_SHORT)
.show();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "解析错误: " + e.getMessage(), Toast.LENGTH_LONG).show();
}
}
在正常的解析操作中,因为HttpResponse 涉及到了泛型,所以需要确定Type。具体可以参照上一篇;这种解析方式的结果如下
面对这种情况应该怎么办?
- 从非技术层面讲,去和你的后端人员好好交流,让他把返回数据该为空,如果你对你的交互能力自信的话。不过话说交际能力强的人还做什么技术?有这能力早做产品去了。程序员说白了不就是无法与人交互,最终只能选择与机器交互?
- 技术层面方法1,Okhttp框架中加一个http拦截器,在Gson解析数据之前,根据返回码,判断是否存在服务端异常,如果异常的话,抛出自定义的服务端异常,或者改造返回的json字符串。之前有用过这种方式,现在回想起来,感觉非常蠢。
- 技术层面方法2,自定义反序列化。遇到服务端异常,直接将返回数据置空;
Gson如何自定义序列化与反序列化,非常简单。
注册一个类实现特定对象的反序列化,实现JsonDeserializaer接口,在反序列化方法中实现自己特定的反序列化逻辑。在下面的代码中,如果接口访问成功,返回原来的数据。否则,提取code与message,返回空data的数据;
public class HttpDeserializer implements JsonDeserializer<HttpResponse> {
@Override
public HttpResponse deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext
context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
int code = jsonObject.get("code").getAsInt();
HttpResponse response = new HttpResponse();
if(code == 200){
response.setCode(jsonObject.get("code").getAsInt());
response.setMessage(jsonObject.get("message").getAsString());
response.setData(jsonObject.get("data"));
}else {
response.setCode(jsonObject.get("code").getAsInt());
response.setMessage(jsonObject.get("message").getAsString());
response.setData(null);
}
return response;
}
}
序列化基本上的操作也是一样的。自定义一个类实现序列化接口,然后在实例化Gson对象的时候注册一下
public class HttpSerializer implements JsonSerializer<HttpResponse> {
@Override
public JsonElement serialize(HttpResponse src, Type typeOfSrc, JsonSerializationContext
context) {
return new JsonPrimitive(new Gson().toJson(src));
}
}
在实例化Gson实例的时候,注册自己的序列化与反序列化实例;
// 自定义的解析数据方法
public void customDeserializer(@NonNull String json) {
try {
GsonBuilder builder = new GsonBuilder();
Type type = new TypeToken<HttpResponse<UserBean>>() {}.getType();
builder.registerTypeAdapter(type, new HttpDeserializer());
Gson gson = builder.create();
HttpResponse response = gson.fromJson(json, type);
Toast.makeText(this, "解析成功: " + response.getData(), Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "解析错误: " + e.getMessage(), Toast.LENGTH_LONG).show();
}
}
自定义解析数据的方法,与上文中的正常的解析数据基本上一直,唯一的不同就是加入了自定义反序列化;具体的效果如下
以上就是Gson自定义序列化与反序列化的所有过程。感觉技术性的东西没说什么,仅仅只是记录了学习的过程。等等,貌似有点问题。一般情况下,在应用程序中我们的Gson一般都是配合Okhttp+Retrofit+RxJava使用的。
public Retrofit retrofit(){
return new Retrofit.Builder()
.baseUrl(HTTP_BASE_URL)
.client(okHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
也许一个应用中,能接触到Gson的代码就这么多,GsonConverterFactory.create(),就这一个api,那我如何将我的自定义序列化与反序列化的对象注册到Gson对象中呢?问的好!点开GsonConverterFactor 这个类,我们发现它有这么一个方法
/**
* Create an instance using {@code gson} for conversion. Encoding to JSON and
* decoding from JSON (when no charset is specified by a header) will use UTF-8.
*/
@SuppressWarnings("ConstantConditions") // Guarding public API nullability.
public static GsonConverterFactory create(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
return new GsonConverterFactory(gson);
}
这个方法可以传入我们自己定义的Gson实例。如果你还不懂怎么操作,那我也没办法了。
以上就是Gson自定义序列化与反序列化的过程。下面,我想去Gson源码里看看,也许那边的世界更精彩!