JsonDeserializer——Gson自定义解析类型错误的字段

来源:互联网 发布:qq群发信息软件 编辑:程序博客网 时间:2024/05/22 17:34

在开发中,定义好实体类和相应字段,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。

2 0