Android数据解析之Gson应用分析

引言

说起终端与网络的数据交互格式,Json肯定比Xml更通用,也更好用,可算轻量级,Json已成我们在实际开发中最常用的数据交换格式,而对于Json的解析,在Android平台上最常用的类库有Gson和FastJson两种。结合项目,本人主要使用 Gson来解析网络返回数据(反序列化操作),可以说,对于Gson的基本使用没有什么问题,但并没有对Gson更深入的认识,因此,在这里做一个知识点的整理。

名词简介

  • Json:Json(JavaScript Object Notation)是一种轻量级的数据交换格式。用于数据转化传输,通用于PHP、Java、C++、C#、Python等编程语言的数据交换传输。它易于人阅读和编写,同时也易于机器解析和生成。

  • Gson:Gson是Google的一个开源库,利用序列化和反序列化技术,实现Json字符串和Java对象间的转换(原文:Gson (also known as Google Gson) is an open source Java library to serialize and deserialize Java objects to (and from) JSON)。

Gson特点

Gson作为开源的简单的序列化和反序列化Java对象的组件,尤其在Android开发中大部分开发人员都使用Gson来做序列化和反序列化组件,应验了行业一句“谷歌出品,必属精品”。下面来说说Gson的特点和作用。
Gson特点:

  • 快速、高效
  • 代码量少、简洁
  • 面向对象
  • 数据传递和解析方便

Gson作用:

  • 提供简单易用的方法比如 toString() ,构造方法来转化Java为Json以及反转化
  • 提供已存在的不可修改对象转化为Json以及反转化
  • 提供对象的惯例表示
  • 支持任意复杂对象
  • 生成健壮、可读的JSON输出

总之,一句话就是:方便快捷强大。

Gson的起步

导入Gson

在build.gradle(Module:app)中加入,当前最新版本为2.8.5:

implementation 'com.google.code.gson:gson:2.8.5'

Gson对象

在进行序列化与反序列操作前,需要先实例化一个Gson 对象,获取 Gson 对象的方法有两种:

//通过构造函数来获取
Gson gson = new Gson();

或者

//通过 GsonBuilder 来获取,可以进行多项特殊配置
Gson gson = new GsonBuilder().create();

我通常使用第一种方式创建。

Gson接口

Gson最基本接口toJson()和fromJson():

gson.toJson(src)            //序列化
gson.fromJson(src,type)     //反序列化

Gson的用法

Gson的基本用法

基本数据类型的生成

由基本数据类型转在Json数据,一般用于组包上行数据。

Gson gson = new Gson();
String jsonNumber = gson.toJson(99);       // 99
String jsonBoolean = gson.toJson(true);    // true
String jsonString = gson.toJson("cchip"); //"String"
基本数据类型的解析

常用的基本数据类型解析有5个:

Gson gson = new Gson();
int i = gson.fromJson("99", int.class);              //99
double d = gson.fromJson("\"99.99\"", double.class);  //99.99
boolean b = gson.fromJson("true", boolean.class);     // true
String str = gson.fromJson("cchip", String.class);   // String

Gson的序列化与反序列化

Gson提供了 toJson() 和 fromJson() 两个方法用于转化 Model 与 Json,前者实现了序列化,后者实现了反序列化。
首先,定义一个实体类:

public class DeviceInfo implements Serializable {
    private String ret;
    private String rssi;
    private String language;
    private String ssid;
    private String MAC;

    //省略

}

简单对象转化

Gson gson = new Gson();
//DeviceInfo -> json
String json = gson.toJson(deviceInfo);
//json->DeviceInfo
DeviceInfo deviceInfo = gson.fromJson(json, DeviceInfo.class);

属性重命名

有时候,我们总是把事情想的太理想,服务端由于疏忽,容易引入大小写字母问题,导致解析出错,如:
理想的数据:

//反序列化
String dataJson = "{"ret":"succ","rssi":"70","language":"en","ssid":"cchip","MAC":"010203040506"}";
DeviceInfo deviceInfo = gson.fromJson(dataJson, DeviceInfo.class);

如果没有和服务器端沟通好或者是 API 改版了,接口返回的数据格式可能是这样的:

String dataJson = "{"Ret":"succ","rssi":"70","language":"en","ssid":"cchip","mac":"010203040506"}";

如果继续使用上一节介绍的方法,那无疑会解析出错。此时为了兼顾多种格式的数据,就需要使用 SerializedName 注解,根据 SerializedName 的声明来看,SerializedName 包含两个属性值,一个是字符串,一个是字符串数组,而字符串数组含有默认值:

public class DeviceInfo implements Serializable {
    @SerializedName("Ret")
    private String ret;
    private String rssi;
    private String language;
    private String ssid;
    private String MAC;

    //省略

}

SerializedName的作用是为了在序列化或反序列化时,指导 Gson 如果将原有的属性名和其它特殊情况下的属性名联系起来。

还有个问题没解决,为了应对多种属性名不一致的情况,难道我们要声明多个 User 类吗?这显然是不现实的,所以还需要为 User 类设置多个备选属性名,这就需要用到 SerializedName 注解的另一个属性值 alternate 了。

public class DeviceInfo implements Serializable {
    @SerializedName(value = "Ret", alternate = {"Ret", "Return"})
    private String ret;
    private String rssi;
    private String language;
    private String ssid;
    private String MAC;

    //省略

}

字段过滤

有时候并不是所有的字段都需要进行系列化和反序列化,因此需要对某些字段进行排除,有四种方法可以来实现这种需求。

基于@Expose注解

Expose 注解包含两个属性值,且均声明了默认值。Expose 的含义即为“暴露”,即用于对外暴露字段,serialize 用于指定是否进行序列化,deserialize 用于指定是否进行反序列化。如果字段不声明 Expose 注解,则意味着不进行序列化和反序列化操作,相当于两个属性值均为 false 。此外,Expose 注解需要和 GsonBuilder 构建的 Gson 对象一起使用才能生效。
Expose 注解的注解值声明情况有四种:

  • @Expose(serialize = true, deserialize = true) //序列化和反序列化都生效
  • @Expose(serialize = false, deserialize = true) //序列化时不生效,反序列化时生效
  • @Expose(serialize = true, deserialize = false) //序列化时生效,反序列化时不生效
  • @Expose(serialize = false, deserialize = false) //序列化和反序列化都不生效,和不写注解一样

例如:

public class DeviceInfo implements Serializable {
    @Expose(serialize = true, deserialize = true)   //序列化和反序列化都生效
    private String ret;
    private String rssi;
    private String language;
    private String ssid;
    private String MAC;

    //省略

}

按照如上的注解值,只有声明了 Expose 注解且 serialize 值为 true 的字段才能被序列化,只有声明了 Expose 注解且 deserialize 值为 true 的字段才能被反序列化。

基于版本

Gson 提供了 @Since 和 @Until 两个注解基于版本对字段进行过滤,@Since 和 @Until 都包含一个 Double 属性值,用于设置版本号。Since 的意思是“自……开始”,Until 的意思是“到……为止”,一样要和 GsonBuilder 配合使用。

当版本( GsonBuilder 设置的版本) 大于或等于 Since 属性值或小于 Until 属性值时字段会进行序列化和反序列化操作,而没有声明注解的字段都会加入序列化和反序列操作。
例如:

public class DeviceInfo implements Serializable {
    @Since(1.4)
    private String ret;
    private String rssi;
    private String language;
    private String ssid;
    private String MAC;

    //省略

}
//反序列化
Gson gson = new GsonBuilder().setVersion(1.6).create();
String dataJson = "{"ret":"succ","rssi":"70","language":true,"ssid":"cchip","MAC":"010203040506"}";
DeviceInfo deviceInfo = gson.fromJson(dataJson, DeviceInfo.class);
基于访问修饰符

访问修饰符由 java.lang.reflect.Modifier 提供 int 类型的定义,而 GsonBuilder 对象的 excludeFieldsWithModifiers方法接收一个 int 类型可变参数,指定不进行序列化和反序列化操作的访问修饰符字段

这种比较少用到,先不举例了,后面再补充。

基于策略

GsonBuilder 类包含 setExclusionStrategies(ExclusionStrategy... strategies)方法用于传入不定长参数的策略方法,用于直接排除指定字段名或者指定字段类型。

这种也比较少用到,先不举例了,后面再补充。

TypeAdapter 自定义(反)序列化

TypeAdapter 是Gson自2.0(源码注释上说的是2.1)开始版本提供的一个泛型抽象类,用于接管某种类型的序列化和反序列化过程,包含两个主要方法 write(JsonWriter,T) 和 read(JsonReader),其它的方法都是final方法并最终调用这两个抽象方法。

public abstract class TypeAdapter<T> {
    public abstract void write(JsonWriter out, T value) throws IOException;
    public abstract T read(JsonReader in) throws IOException;
    //其它final方法就不贴出来了,包括toJson、toJsonTree、fromJson、fromJsonTree和nullSafe等方法。
}

注意:TypeAdapter 以及 JsonSerializer 和 JsonDeserializer 都需要与 .registerTypeAdapter 或 .registerTypeHierarchyAdapter 配合使用,下面将不再重复说明。

之前已经说过Gson有一定的容错机制,比如将字符串 "ret" 写成 “Ret”,但如果有些情况下给你返了个空字符串怎么办?虽然这是服务器端的问题,但这里我们只是做一个示范,不改服务端的逻辑我们怎么容错。
根据我们上面介绍的,我只需注册一个 TypeAdapter 把它的序列化和反序列化过程接管就行了:

public class DeviceInfoTypeAdapter extends TypeAdapter<User> {

    @Override
    public void write(JsonWriter jsonWriter, DeviceInfo deviceInfo) throws IOException {

        //流式序列化成对象开始

        jsonWriter.beginObject();

        //将Json的Key值都指定为大写字母开头
        jsonWriter.name("Rssi").value(deviceInfo.getRssi());
        //流式序列化结束
        jsonWriter.endObject();
    }

    @Override
    public User read(JsonReader jsonReader) throws IOException {
        DeviceInfo deviceInfo = new DeviceInfo();
        //流式反序列化开始
        jsonReader.beginObject();

        while (jsonReader.hasNext()) {
            switch (jsonReader.nextName()) {
                //首字母大小写均合法
                case "ret":
                case "Ret":
                    deviceInfo.setRet(jsonReader.nextString());
                    break;
                case "rssi":
                    deviceInfo.setRssi(jsonReader.nextString());
                    break;
            }

        }

        //流式反序列化结束
        jsonReader.endObject();
        return deviceInfo;

    }

}

TypeAdapterFactory

TypeAdapterFactory,见名知意,用于创建 TypeAdapter 的工厂类。通过参数 TypeToken 来查找确定对应的 TypeAdapter,如果没有就返回 null 并由 Gson 默认的处理方法来进行序列化和反序列化操作,否则就由用户预定义的 TypeAdapter 来进行处理。
使用方式:与GsonBuilder.registerTypeAdapterFactory配合使用,通过对比Type,确定有没有对应的TypeAdapter,没有就返回null,有则返回(并使用)自定义的TypeAdapter。

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(new TypeAdapterFactory() {
        @Override
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
            if (type.getType() == Integer.class || type.getType() == int.class)                         return intTypeAdapter;
            return null;
        }
    })

JsonSerializer 和 JsonDeserializer

JsonSerializer 和JsonDeserializer 不用像TypeAdapter一样,必须要实现序列化和反序列化的过程,你可以据需要选择,如只接管序列化的过程就用 JsonSerializer ,只接管反序列化的过程就用 JsonDeserializer ,如上面的需求可以用下面的代码。

    Gson gson = new GsonBuilder().registerTypeAdapter(DeviceInfo.class, new JsonDeserializer<DeviceInfo>() {

        @Override
        public DeviceInfo deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
            JsonObject jsonObject = jsonElement.getAsJsonObject();

            String ret = null;
            //同时支持 ret 和 Ret 两种情况
            if (jsonObject.has("Ret")) {
                name = jsonObject.get("Ret").getAsString();
            } else if (jsonObject.has("ret")) {
                name = jsonObject.get("ret").getAsString();
            }

            String rssi = jsonObject.get("rssi").getAsString();
            String ssid = jsonObject.get("ssid").getAsString();
            return new DeviceInfo(...);
        }
    }).create();

Gson常用的没几个,但用处或用法却博大精深,还需看各个的悟性!


参考资料: