1. 程式人生 > >JsonDeserializer——Gson自定義解析型別錯誤的欄位

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就會使用自定義的ADeserializerBDeserializer分別反序列化類A和類B。

`registerTypeAdapter 方法用於配置Gson的序列化或反序列化,接收兩個引數。

第一個引數是註冊的型別,即希望自定義解析的資料型別。

第二個引數包含了自定義的解析過程,它必須至少是TypeAdapterInstanceCreatorJsonSerializerJsonDeserializer四者之一的實現。

JsonDeserializer

泛型介面JsonDeserializer中的方法deserialize允許我們自定義反序列化過程,返回相對應的物件。

該方法接收三個引數,Gson會在進行相應型別欄位的反序列化時回撥該方法。

第一個引數型別為JsonElement,其中包含了真實返回的資料,它是一個抽象類,可以是JsonObjectJsonArrayJsonPrimitiveJsonNull四者之一,看名字也能知道這四者的大致型別。

第二個引數是當前反序列化的資料型別。

第三個是上下文引數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

之後分別設定欄位sizeapps,其中用到的一些方法和上面類似。

JsonDeserializer自定義反序列化的思路大致就是這樣,以後處理類似錯誤自當666。