JsonDeserializer——Gson自定義解析型別錯誤的欄位
在開發中,定義好實體類和相應欄位,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。