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。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值