在开发中,定义好实体类和相应字段,Gson就可以很方便地帮助我们实现序列化和反序列化。
可是有时候,后台传给客户端的json数据格式有误,其中的某些字段类型错误,即,和我们在实体类中定义的字段类型不一致,此时就会出现类型转换错误,app原地爆炸!
假设有这么一个类Phone
代表手机:
/**
* Author: Sbingo
* Date: 2017/4/23
*/
public class Phone {
/**
* 手机大小
*/
private Size size;
/**
* 手机内app列表
*/
private List<App> apps;
public Size getSize() {
return size;
}
public void setSize(Size size) {
this.size = size;
}
public List<App> getApps() {
return apps;
}
public void setApps(List<App> apps) {
this.apps = apps;
}
}
其中类Size
如下:
/**
* Author: Sbingo
* Date: 2017/4/23
*/
class Size {
private String length;
private String width;
private String height;
public String getLength() {
return length;
}
public void setLength(String length) {
this.length = length;
}
public String getWidth() {
return width;
}
public void setWidth(String width) {
this.width = width;
}
public String getHeight() {
return height;
}
public void setHeight(String height) {
this.height = height;
}
}
类App
如下:
/**
* Author: Sbingo
* Date: 2017/4/23
*/
class App {
private String appName;
private String developer;
public String getAppName() {
return appName;
}
public void setAppName(String appName) {
this.appName = appName;
}
public String getDeveloper() {
return developer;
}
public void setDeveloper(String developer) {
this.developer = developer;
}
}
对于类Phone
,实际中我碰到的数据类型错误情况有:
对象size变成了字符串;
数组apps变成了字符串;
下面将会针对这两种错误分别讨论,其他错误可以类似处理。
当然这里的字符串是带有转义字符的json字符串,还是包含正确信息的。因此以下对字符串的处理可能只适应这种错误情况。
如何解决这一问题?
最好的办法当然是后台确保数据类型正确,从源头上消灭问题。
作为客户端开发,我们也要做好错误处理,应对各种场景。
客户端怎么处理?
registerTypeAdapter
其实Gson允许我们自定义解析某种类型的数据,一般我们使用Gson可能就直接new出来一个,但如果这样实例化:
Gson gson = new GsonBuilder()
.registerTypeAdapter(A.class, new ADeserializer())
.registerTypeAdapter(B.class, new BDeserializer())
.create()
得到的gson就会使用自定义的ADeserializer
和BDeserializer
分别反序列化类A和类B。
`registerTypeAdapter 方法用于配置Gson的序列化或反序列化,接收两个参数。
第一个参数是注册的类型,即希望自定义解析的数据类型。
第二个参数包含了自定义的解析过程,它必须至少是TypeAdapter
、InstanceCreator
、JsonSerializer
、JsonDeserializer
四者之一的实现。
JsonDeserializer
泛型接口JsonDeserializer
中的方法deserialize
允许我们自定义反序列化过程,返回相对应的对象。
该方法接收三个参数,Gson会在进行相应类型字段的反序列化时回调该方法。
第一个参数类型为JsonElement,其中包含了真实返回的数据,它是一个抽象类,可以是JsonObject
、JsonArray
、JsonPrimitive
、JsonNull
四者之一,看名字也能知道这四者的大致类型。
第二个参数是当前反序列化的数据类型。
第三个是上下文参数context
。
在自定义反序列化时,我们要分别处理数据类型正确和错误的情况,具体处理过程视数据而定,以下仅供参考。
错误一:对象size变成了字符串
字段size为Size对象类型,当后台接口返回了字符串时,可以只对Size类型自定义反序列化,如下:
/**
* Author: Sbingo
* Date: 2017/4/23
*/
public class SizeDeserializer implements JsonDeserializer<Size> {
@Override
public Size deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Size size = new Size();
if (json.isJsonObject()) {
//类型正确
JsonObject jsonObject = json.getAsJsonObject();
size.setLength(jsonObject.get("length").getAsString());
size.setWidth(jsonObject.get("width").getAsString());
size.setHeight(jsonObject.get("height") == null ? "" : jsonObject.get("height").getAsString());
} else {
//类型错误
String value = json.getAsString();
size = new Gson().fromJson(value, Size.class);
}
return size;
}
}
泛型参数传入Size,当反序列化Size对象时,就会回调我们自定义的方法。
首先用isJsonObject()
判断是否为对象
如果是,说明类型正确,接着依次设置各字段值。如果有些字段接口可能不会返回,记得判空,例如字段height
。
如果类型错误,我遇到的错误情况只会是带有转义字符的字符串,所以直接按此处理了。调用json.getAsString()
就能得到正确的json字符串,再通过Gson直接转换为Size类型。
这样不管接口返回数据中字段size
类型正确与否,都能从容应对!
错误二:数组apps变成了字符串
字段apps是一个list,接口应该返回一个数组,如果类型错误,可以有两种方式来自定义解析,在我看来,这两种各有优劣,具体使用要结合实际。
自定义解析字段本身
这种方法和解析size
类似,都是本着“谁类型错了,就解析谁”的原则。
由于没有找到用List
作为泛型参数的方法,所以需要把类Phone
中的字段apps
从list改成数组,如果真只能改成数组,使用方便性可能较list略差。
定义AppDeserializer
如下:
/**
* Author: Sbingo
* Date: 2017/4/23
*/
public class AppDeserializer implements JsonDeserializer<App[]> {
@Override
public App[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonArray()) {
//类型正确
JsonArray jsonArray = json.getAsJsonArray();
App[] apps = new App[jsonArray.size()];
for (int i = 0; i < jsonArray.size(); i++) {
App app = new App();
//获取app方法一
JsonObject jsonObject = jsonArray.get(i).getAsJsonObject();
app.setAppName(jsonObject.get("appName") == null ? "" : jsonObject.get("appName").getAsString());
app.setDeveloper(jsonObject.get("developer") == null ? "" : jsonObject.get("developer").getAsString());
//获取app方法二
// app = context.deserialize(jsonObject, App.class);
apps[i] = app;
}
return apps;
} else if (json.isJsonObject()) {
//类型错误1
return null;
} else if (json.isJsonPrimitive()) {
//类型错误2,多为String
String value = "";
try {
value = json.getAsString();
} catch (Exception e) {
e.printStackTrace();
}
if ("".equals(value)) {
return null;
} else {
App[] apps = new Gson().fromJson(value, App[].class);
return apps;
}
} else {
//一般不出现这种情况
return null;
}
}
}
泛型参数传入数组,当反序列化App类型的数组时,就会回调我们自定义的方法。
首先用isJsonArray()
类型是否正确,正确时获取数组也有两种方法,如代码所示,
方法一和之前的分析差不多。
方法二注意context
反序列化时要避免进入死循环。
类型错误时做了较多区分,具体还是要看实际情况。
自定义解析字段所在类
这种方法以错误字段apps所在类Phone为处理类型,好处是字段apps还是可以用list,但如果类中很多其他字段,工作量就大了一些,幸好此处只还有一个size。
定义AppListDeserializer
如下:
/**
* Author: Sbingo
* Date: 2017/4/23
*/
public class AppListDeserializer implements JsonDeserializer<Phone> {
@Override
public Phone deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Phone phone = new Phone();
JsonObject jsonObject = json.getAsJsonObject();
//此时不考虑size的类型错误情况
if (jsonObject.get("size") != null) {
jsonObject = jsonObject.get("size").getAsJsonObject();
Size size = context.deserialize(jsonObject, Size.class);
phone.setSize(size);
}
JsonElement appsArray = jsonObject.get("apps");
if (appsArray != null) {
List<App> apps = new ArrayList<>();
if (appsArray.isJsonArray()) {
//类型正确
JsonArray jsonArray = appsArray.getAsJsonArray();
for (int i = 0; i < jsonArray.size(); i++) {
JsonObject jo = jsonArray.get(i).getAsJsonObject();
App app = context.deserialize(jo, App.class);
apps.add(app);
}
} else {
//类型错误
String value = appsArray.getAsString();
App[] a = new Gson().fromJson(value, App[].class);
apps = Arrays.asList(a);
}
phone.setApps(apps);
}
return phone;
}
}
泛型参数传入Phone,当反序列化Phone类型数据时,就会回调我们自定义的方法。
因为这里处理的是字段apps
,所以不考虑字段size
的类型正确与否。
Phone可以确定是个对象,所以直接用getAsJsonObject()
方法获取一个JsonObject
。
之后分别设置字段size
和apps
,其中用到的一些方法和上面类似。
JsonDeserializer自定义反序列化的思路大致就是这样,以后处理类似错误自当666。