Android筑基——Gson 框架学习笔记

1 前言

在 Android 开发中,常使用 Gson 框架来进行 Java 对象的序列化与 json 数据的反序列化。

本文会通过实际的例子逐步深入地展示 Gson 框架的使用,希望能够帮助同学们更好地开发。

2 正文

2.1 基本使用

使用 Gson 对象的 toJson()fromJson() 来进行 Java 对象的序列化,json 数据的反序列化。
打开 Gson 类的代码,可以看到有多组 toJson() 以及 fromJson() 方法,它们之间的调用关系如下图所示(请点击大图查看):
在这里插入图片描述
在这里插入图片描述
看完上面的调用关系图,我们接着看一下 toJson()fromJson() 方法的使用吧。

简单的类

首先定义一个简单的类,Person 类:

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

测试代码如下:

public class Test01 {
    public static void main(String[] args) {
        Gson gson = new Gson();
        // 序列化
        Person person = new Person("willwaywang6", 18);
        String personJson = gson.toJson(person);
        System.out.println(personJson);

        // 反序列化
        String json = "{\"name\":\"willwaywang6\",\"age\":18}";
        Person p = gson.fromJson(json, Person.class);
        System.out.println(p);
    }
}

打印结果如下:

{"name":"willwaywang6","age":18}
Person{name='willwaywang6', age=18}

可以看到,通过创建一个 Gson 对象,使用 toJson()fromJson() 就可以实现基本的序列化与反序列化了。

泛型类

那么,对于泛型类,还使用上面的方式处理,还会适用吗?

我们引入一个泛型类:

class Response<T> {
    public int code;
    public String message;
    public T data;

    @Override
    public String toString() {
        return "Response{" +
                "code=" + code +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }
}

这样的一个泛型类,在开发中常常作为接收服务端返回的数据。

先进行序列化的测试,使用 Person 来作为泛型类型参数的实参:

Gson gson = new Gson();
// 序列化
Person person = new Person("willwaywang6", 18);
Response<Person> response = new Response<>();
response.code = 0;
response.message = "success";
response.data = person;
String responseJson = gson.toJson(response);
System.out.println(responseJson);

打印结果如下:

{"code":0,"message":"success","data":{"name":"willwaywang6","age":18}}

可以看到,对于包含泛型的类,序列化是没有问题的。

接着看反序列化:

Gson gson = new Gson();
// 反序列化
String json = "{\"code\":0,\"message\":\"success\",\"data\":{\"name\":\"willwaywang6\",\"age\":18}}";
Response<Person> r = gson.fromJson(json, Response.class);
System.out.println(r);

打印结果如下:

Response{code=0, message='success', data={name=willwaywang6, age=18.0}}

貌似也是正常的吧?

不不不,好像有点不对啊?我们知道 age 的数据类型是 int 类型,但是打印出的怎么是 age=18.0 呢?

这是怎么回事呢?

为了一探究竟,我们就在 System.out.println(r); 这行打一个断点,debug 一下吧。

在这里插入图片描述
看我们的截图,age 的类型确实变成了 Double 类型,关键的是 Response 对象的data 字段,它的类型是 LinkedTreeMap,而不是我们期望的 Person 类型。

可能有同学会想,输出仅仅是有一个 age 的类型不正确而已,还是正常地反序列化了吧?不用再做别的事情了吧?

为了说明这种想法的错误,我们再添加一行代码:

Person p2 = r.data;

目的是为了取出 Response 对象的 data 字段里的 Person 对象,以便程序里面进一步使用。
运行程序,会看到如下报错信息:

java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.example.lib.basic.Person
	at com.example.lib.basic.Test02.main(Test02.java:31)

类型转换异常:不能把 LinkedTreeMap 类型转换为 Person 类型。

这个异常的发生是很明显的,从 debug 截图里我们可以知道,data 字段目前的类型是 LinkedTreeMap,这里把 LinkedTreeMap 类型转换为 Person 类型,当然是不可以的。

因此,对于 json 需要反序列化为泛型对象,不能直接使用 public <T> T fromJson(String json, Class<T> classOfT) 这个方法了。

解决反序列化为泛型类的异常

我们使用 public <T> T fromJson(String json, Type typeOfT) 这个方法来解决反序列化时失败的问题。

看如下测试代码:

Gson gson = new Gson();
// 反序列化
String json = "{\"code\":0,\"message\":\"success\",\"data\":{\"name\":\"willwaywang6\",\"age\":18}}";
Type responseType = new TypeToken<Response<Person>>() {
}.getType();
Response<Person> r = gson.fromJson(json, responseType);
System.out.println(r);
System.out.println(r.data);
try {
    Person p2 = r.data;
} catch (Exception exception) {
    exception.printStackTrace();
}

可以看到,我们先获取了 Type responseType 对象:

Type responseType = new TypeToken<Response<Person>>() {
}.getType();

接着把 responseType 传入了 fromJson(String json, Type typeOfT) 方法:

Response<Person> r = gson.fromJson(json, responseType);

运行一下程序,查看结果:

Response{code=0, message='success', data=Person{name='willwaywang6', age=18}}
Person{name='willwaywang6', age=18}

为了更细致地查看一下,这是还是打断点查看一下返回结果吧。

在这里插入图片描述
No problem!

到这里,gson 框架的基本使用就完成了。

但是,实际开发中,满足于 gson 的基本使用是不够的。gson 也为我们提供了一些配置来解决实际开发中的问题。

2.2 使用 Gson 框架提供的注解

@SerializedName 注解

看一下 @SerializedName 注解的定义:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface SerializedName {

  /**
   * @return the desired name of the field when it is serialized or deserialized
   * 当进行序列化或反序列化时,返回期望的字段名字
   */
  String value();
  /**
   * @return the alternative names of the field when it is deserialized
   * 当进行反序列化时,返回可选的字段名字
   */
  String[] alternate() default {};
}

下面通过例子来进行说明:

现在有一个简单的 Response 类:

class Response {
    
    public int code;
    public String message;
    public String data;

    @Override
    public String toString() {
        return "Response{" +
                "code=" + code +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }
}

需求是把一个 Response 对象序列化为 json 后传递给服务端解析,服务端要求的 json 是这样的:

{
	"code": 0,
	"msg": "ok",
	"content": "some data"
}

如果我们不做什么处理,对 Response 对象进行序列化是不能满足要求的。

Gson gson = new Gson();
// 序列化
Response response = new Response(0, "ok", "some data");
String responseJson = gson.toJson(response);
System.out.println(responseJson);

打印结果如下:

{"code":0,"message":"ok","data":"some data"}

这与服务端要求的 json 格式是不一致的。

这时就需要用到 @SerializedName 注解的 value 元素了,在 messagedata 字段上分别添加注解,如下所示:

class Response {
    public int code;
    @SerializedName("msg")
    public String message;
    @SerializedName("content")
    public String data;
}

再次运行程序,运行结果如下:

{"code":0,"msg":"ok","content":"some data"}

可以看到满足了需求。

说完了序列化,我们遇到了一个反序列化的需求,希望把

{"code":0,"msg":"ok","content":"some data"}

反序列化一个 Response 对象,注意这时的 Response 类还没有使用任何注解。

class Response {
    public int code;
    public String message;
    public String data;
}

测试代码如下:

Gson gson = new Gson();
// 反序列化
String json = "{\"code\":0,\"msg\":\"ok\",\"content\":\"some data\"}";
Response r = gson.fromJson(json, Response.class);
System.out.println(r);

打印结果如下:

Response{code=0, message='null', data=null}

显然没有反序列化成功。

解决办法仍是在 messagedata 字段上分别添加注解,如下所示:

class Response {
    public int code;
    @SerializedName("msg")
    public String message;
    @SerializedName("content")
    public String data;
}

再次运行程序,查看结果:

Response{code=0, message='ok', data=some data}

小结一下:

  • 当把 Java 对象序列化为 json 字符串时,类中声明了该注解的字段会使用 @SerializedName 注解指定的 value 元素值作为 json 字符串中对应的 key;

  • 当把 json 字符串反序列化为 Java 对象时,以 @SerializedName 注解指定的 value 元素值作为 key 的 value 值,会赋值给类中声明了该注解的字段。

细心的你,有没有注意到了 @SerializedName 注解还有一个 alternate 元素?这个也十分有用,但它只作用在反序列化的过程中。

通过一个场景来说明 alternate 元素的使用吧。

现在服务端下发了这样的三组 json 字符串:

{
	"code": 0,
	"msg": "ok",
	"content": "some data"
}
{
	"code": 1,
	"msg": "server bang",
	"result": "blablabla"
}
{
	"code": 2,
	"msg": "server bang",
	"result_data": "blablabla"
}

看一下上面的三个 json 字符串,它们的 key 是不同的,分别为 contentresultresult_data,在客户端它们都对应于 Response 类,因此客户端需要把它们都序列化为 Response 对象。

class Response {
    public int code;
    @SerializedName("msg")
    public String message;
    @SerializedName("content")
    public String data;
}

来进行一下测试,查看结果:

Gson gson = new Gson();
String json1 = "{\"code\":0,\"msg\":\"ok\",\"content\":\"some data\"}";
String json2 = "{\"code\":1,\"msg\":\"server bang\",\"result\":\"blablabla\"}";
String json3 = "{\"code\":2,\"msg\":\"server bang\",\"result_data\":\"blablabla\"}";
System.out.println(gson.fromJson(json1, Response.class));
System.out.println(gson.fromJson(json2, Response.class));
System.out.println(gson.fromJson(json3, Response.class));

打印结果:

Response{code=0, message='ok', data=some data}
Response{code=1, message='server bang', data=null}
Response{code=2, message='server bang', data=null}

可以看到,后面两个 json 字符串并没有成功反序列化 Java 对象的 data 字段。

这时就需要用到 @SerializedName 注解的 alternate 元素了。

class Response {
    public int code;
    @SerializedName("msg")
    public String message;
    @SerializedName(value = "content", alternate = {"result", "result_data"})
    public String data;
}

再次运行,查看结果:

Response{code=0, message='ok', data=some data}
Response{code=1, message='server bang', data=blablabla}
Response{code=2, message='server bang', data=blablabla}

可以看到,成功地完成了反序列化。

如果出现了这样的 json 字符串,会如何反序列化呢?

{
	"code": 4,
	"msg": "server bang",
	"content": "some data",
	"result": "blablabla",
	"reslut_data": "plaplapla"
}
Gson gson = new Gson();
// 出现多个备选时的反序列化
String json4 = "{\"code\":4,\"msg\":\"server bang\",\"content\":\"some data\",\"result\":\"blablabla\",\"result_data\":\"plaplapla\"}";
String json5 = "{\"code\":5,\"msg\":\"server bang\",\"result\":\"blablabla\",\"result_data\":\"plaplapla\",\"content\":\"some data\"}";
String json6 = "{\"code\":6,\"msg\":\"server bang\",\"result_data\":\"plaplapla\",\"content\":\"some data\",\"result\":\"blablabla\"}";
System.out.println(gson.fromJson(json4, Response.class));
System.out.println(gson.fromJson(json5, Response.class));
System.out.println(gson.fromJson(json6, Response.class));

打印结果如下:

Response{code=4, message='server bang', data=plaplapla}
Response{code=5, message='server bang', data=some data}
Response{code=6, message='server bang', data=blablabla}

查看打印结果,当 json 字符串中出现了 alternate 数组里的多个元素时,会选取最后一个赋值给类中声明了该注解的字段。

小结一下:

当把 json 字符串反序列化为 Java 对象时,以 @SerializedName 注解指定的 alternate 元素数组里的元素作为 key 的 value 值,都会赋值给类中声明了该注解的字段。

当 json 字符串中出现了 alternate 数组里的多个元素时,会选取最后一个赋值给类中声明了该注解的字段。

@Expose 注解

使用 Expose 注解,可以明显地指定某个字段是否可以序列化或反序列化。

看下面的类:

public class Person {
    @Expose
    String firstName;
    @Expose(deserialize = false)
    String lastName;
    @Expose(serialize = false)
    int age;
    @Expose(serialize = false, deserialize = false)
    String password;
    String phoneNumber;
}

先说明各个字段参与序列化与反序列化的情况:

字段名参与序列化?参与反序列化?
firstName
lastName×
age×
password××
phoneNumber××

测试代码如下:

// 序列化
Person person = new Person("zhichao", "wang", 18, "123456", "13912345678");
Gson gson = new Gson();
System.out.println(gson.toJson(person));
// 反序列化
String json = "{\"firstName\":\"zhichao\",\"lastName\":\"wang\",\"age\":18,\"password\":\"123456\",\"phoneNumber\":\"13912345678\"}";
System.out.println(gson.fromJson(json, Person.class));

运行结果:

{"firstName":"zhichao","lastName":"wang","age":18,"password":"123456","phoneNumber":"13912345678"}
Person{firstName='zhichao', lastName='wang', age=18, password='123456', phoneNumber='13912345678'}

没有看到使用了 @Expose 注解的效果,所有的字段都参与了序列化与反序列化的过程。

这是因为构建 Gson 对象的方式以及没有添加 excludeFieldsWithoutExposeAnnotation() 配置,修改创建 Gson 对象的代码为:

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

再次运行程序,如下:

{"firstName":"zhichao","lastName":"wang"}
Person{firstName='zhichao', lastName='null', age=18, password='null', phoneNumber='null'}

对照上面给出的表格,再看一下:

字段名参与序列化?参与反序列化?
firstName
lastName×
age×
password××
phoneNumber××

参与序列化的字段有 firstNamelastName,因此序列化后的 json 字符串是:

{"firstName":"zhichao","lastName":"wang"}

参与反序列化的字段有 firstNameage,因此反序列化后的对象打印是:

Person{firstName='zhichao', lastName='null', age=18, password='null', phoneNumber='null'}

@Exposetransient 关键字的区别:

声明了 @Expose 注解的字段,可以单独地控制是否进行序列化和是否进行反序列化;
而使用 transient 关键字修饰的字段,既不可以序列化,也不可以反序列化。

2.3 使用 Gson 框架提供的配置

使用 serializeNulls() 配置,强制序列化 null

现在有一个请求类 RequestBean

public class RequestBean {
    /**
     * 文章 id
     */
    public int id;
    /**
     * 文章标题
     */
    public String title;
    /**
     * 用户喜欢,则为 true;用户不喜欢,则为 false;用户没有选择,则为 null
     */
    public Boolean action;
}

要求是三个字段的值,都必须序列化后报给服务端。

这个需求不是很难吧,看代码:

Gson gson = new Gson();
RequestBean requestBean = new RequestBean();
requestBean.id = 1;
requestBean.title = "国士无双";
System.out.println(gson.toJson(requestBean));

打印结果如下:

{"id":1,"title":"国士无双"}

但是,序列化后的结果与需求不符号,因为里面并没有包含 action 字段的值,服务端肯定会找过来的。

这时就要用到 serializeNulls() 配置,修改获取 Gson 对象的代码,如下:

Gson gson = new GsonBuilder().serializeNulls().create();

使用 setPrettyPrinting() 配置,格式化 json 字符串打印

现在有一个 UserBean 类:

public class UserBean {
    public String userid;
    public String username;
    public String password;
    public int age;
    public int height;
    public double salary;
    public String address;
}

需要打印它的对象序列化后的 json 字符串:

UserBean userBean = new UserBean();
userBean.userid = "204895272048";
userBean.username = "奥特曼";
userBean.age = 1000;
userBean.height = 300;
userBean.password = "1234567890";
userBean.salary = 30000.0;
userBean.address = "outer space, unknown";
Gson gson = new Gson();
System.out.println(gson.toJson(userBean));

打印结果:

{"userid":"204895272048","username":"奥特曼","password":"1234567890","age":1000,"height":300,"salary":30000.0,"address":"outer space, unknown"}

看到打印结果没有格式化,期望的是这样的:

在这里插入图片描述
通过使用 setPrettyPringint() 配置,修改获取 Gson 对象的方式:

Gson gson = new GsonBuilder().setPrettyPrinting().create();

打印结果:

{
  "userid": "204895272048",
  "username": "奥特曼",
  "password": "1234567890",
  "age": 1000,
  "height": 300,
  "salary": 30000.0,
  "address": "outer space, unknown"
}

使用disableHtmlEscaping() 配置,去除 html 字符转义

现在有一个简单的类:

public class SimpleBean {
    public String title;
    public String message;
}

测试代码如下:

SimpleBean bean = new SimpleBean();
bean.title = "Mr Bush's House";
bean.message = "a > b";
Gson gson = new Gson();
System.out.println(gson.toJson(bean));

打印结果如下:

{"title":"Mr Bush\u0027s House","message":"a \u003e b"}

可以看到 ' 字符被转义成了 \u0027> 字符被转义成了 \u003e

这样的内容直接显示在手机屏幕上,用户指定看不懂。因此,必须改,可以使用 disableHtmlEscaping(),修改获取 Gson 对象的代码为:

Gson gson = new GsonBuilder().disableHtmlEscaping().create();

重新运行后,结果如下:

{"title":"Mr Bush's House","message":"a > b"}

使用 setExclusionStrategies(ExclusionStrategy... strategies) 配置,添加灵活的排除策略

之前的 @Expose 主要针对单个的字段来设置是否序列化与是否反序列化,这里可以通过 setExclusionStrategies(ExclusionStrategy... strategies) 统一配置排除策略。

下面具体进行演示说明:

这是一个简单的类:

public class Student {
    public String name;
    public int age;
    public boolean gender;
    public Date birthday;
    public String _school;
}

我们打算序列化与反序列化都排除掉 boolean 类型和 Date 类型的字段,可以这么做:

Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        return false;
    }
    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        return clazz == Date.class || clazz == boolean.class;
    }
}).create();
// 序列化
Student student = new Student();
student.name = "willwaywang6";
student.age = 18;
student.gender = true;
student.birthday = new Date(System.currentTimeMillis());
student._school = "college";
System.out.println(gson.toJson(student));
// 反序列化
String json = "{\"name\":\"willwaywang6\",\"age\":18,\"gender\":true,\"birthday\":\"May 30, 2021 1:58:25 PM\",\"_school\":\"college\"}";
Student s = gson.fromJson(json, Student.class);
System.out.println(s);

打印结果:

{"name":"willwaywang6","age":18,"_school":"college"}
Student{name='willwaywang6', age=18, gender=false, birthday=null, _school='college'}

如果还想基于字段的名字,注解,修饰符来排除序列化与反序列化,可以在 shouldSkipField 方法里面处理,修改代码如下:

public boolean shouldSkipField(FieldAttributes f) {
	// 排除名字以 "_" 开头的字段
    return f.getName().startsWith("_");
}

重新运行,查看结果:

{"name":"willwaywang6","age":18}
Student{name='willwaywang6', age=18, gender=false, birthday=null, _school='null'}

可以看到:_school 字段没有参与序列化与反序列化了。

2.4 自定义 Gson 的序列化与反序列化

这里指的就是通过 registerTypeAdapter 的方式:

public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) {
  $Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer<?>
      || typeAdapter instanceof JsonDeserializer<?>
      || typeAdapter instanceof InstanceCreator<?>
      || typeAdapter instanceof TypeAdapter<?>);
  if (typeAdapter instanceof InstanceCreator<?>) {
    instanceCreators.put(type, (InstanceCreator) typeAdapter);
  }
  if (typeAdapter instanceof JsonSerializer<?> || typeAdapter instanceof JsonDeserializer<?>) {
    TypeToken<?> typeToken = TypeToken.get(type);
    factories.add(TreeTypeAdapter.newFactoryWithMatchRawType(typeToken, typeAdapter));
  }
  if (typeAdapter instanceof TypeAdapter<?>) {
    factories.add(TypeAdapters.newFactory(TypeToken.get(type), (TypeAdapter)typeAdapter));
  }
  return this;
}

这个方法的作用就是配置 Gson 进行自定义的序列化与反序列化。

这里把整个方法都贴出来,是为了让大家看到虽然方法的名字叫做 registerTypeAdapter,但是可以传入的类型可不止 TypeAdapter,具体来说是可以传入四种类型的对象:

  • JsonSerializer 类型;
  • JsonDeserializer 类型;
  • InstanceCreator 类型;
  • TypeAdapter 类型。

下面会分别演示每一种类型的实际案例,但是只是实际中的一种应用,而非所有的应用,更多的应用方式还需要大家在实际开发中“因地制宜”,灵活运用。

使用 JsonSerializer 自定义序列化

这里构建一个买家,选择商品,最后生成订单,序列化后发给服务器的过程。

先看一下会使用到的几个类:

/**
 * 买家
 */
public class Shopper {
    public String userid;
    public String username;
}
/**
 * 商品
 */
public class Commodity {
    public String id;
    public String name;
}
/**
 * 订单
 */
public class Order {
    public String orderid;
    public String userid;
    public String username;
    public List<Commodity> commodities;
}

演示代码在这里:

Shopper shopper = new Shopper("userid_1234", "willwaywang6");
List<Commodity> commodities = Arrays.asList(
        new Commodity("id_12345", "Huawei P50"),
        new Commodity("id_23456", "Huawei P60")
);
Order order = new Order("order_1111", shopper.userid, shopper.username, commodities);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
System.out.println(gson.toJson(order));

运行结果如下:

{
  "orderid": "order_1111",
  "userid": "userid_1234",
  "username": "willwaywang6",
  "commodities": [
    {
      "id": "id_12345",
      "name": "Huawei P50"
    },
    {
      "id": "id_23456",
      "name": "Huawei P60"
    }
  ]
}

到这里,我们似乎已经完成了需求。

但是,不一会儿,服务端找过来了,说:“客户端的同学,能不能修改一下上报订单的数据格式啊?现在上报的数据有些冗余,服务器会爆炸的,帮忙修改成这样:”

{
	"orderid": "order_1111",
	"userid": "userid_1234",
	"username": "willwaywang6",
	"commodities": [
		"id_12345", "id_23456"
	]
}

首先想到的是,针对单个 Commodity 对象,只序列化 id 字段,修改代码如下:

JsonSerializer<Commodity> jsonSerializer = new JsonSerializer<Commodity>() {
    @Override
    public JsonElement serialize(Commodity src, Type typeOfSrc, JsonSerializationContext context) {
        JsonObject result = new JsonObject();
        result.addProperty("id", src.id);
        return result;
    }
};
Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(Commodity.class, jsonSerializer).create();
System.out.println(gson.toJson(order));

运行结果如下:

{
  "orderid": "order_1111",
  "userid": "userid_1234",
  "username": "willwaywang6",
  "commodities": [
    {
      "id": "id_12345"
    },
    {
      "id": "id_23456"
    }
  ]
}

不符合要求。

其实,应该拿到整个 List<Commodity> 对象,然后取出里面的 id,存放在一个数组里面,修改代码如下:

JsonSerializer<List<Commodity>> jsonSerializer = new JsonSerializer<List<Commodity>>() {
    @Override
    public JsonElement serialize(List<Commodity> src, Type typeOfSrc, JsonSerializationContext context) {
        JsonArray jsonArray = new JsonArray();
        for (Commodity commodity : src) {
            jsonArray.add(commodity.id);
        }
        return jsonArray;
    }
};
// 注意这里有泛型,要这样获取 type。
Type type = new TypeToken<List<Commodity>>() {
}.getType();
Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(type, jsonSerializer).create();
System.out.println(gson.toJson(order));

运行结果如下:

{
  "orderid": "order_1111",
  "userid": "userid_1234",
  "username": "willwaywang6",
  "commodities": [
    "id_12345",
    "id_23456"
  ]
}

哈哈,这次满足了服务端的要求了。

使用 JsonDeserializer 自定义反序列化

这次的需求是把后台给的 json 字符串反序列化为对象:

{
	"name": "Huawei P100",
	"weight": 1,
	"timestamp": 1622360677020
}

对应的类为:

public class Goods {
    public String name;
    public int weight;
    public long timestamp;
}

测试代码如下:

String goodsJson = "{\"name\":\"Huawei P100\",\"weight\":1,\"timestamp\":1622360677020}";
Gson gson = new GsonBuilder().create();
Goods goods = gson.fromJson(goodsJson, Goods.class);
System.out.println(goods);

打印结果:

Goods{name='Huawei P100', weight=1, timestamp=1622360677020}

正常地进行了反序列化。

但是,后台有时候返回了这样的 json 字符串:

{
	"name": "Huawei P100",
	"weight": "",
	"timestamp": 1622360677020
}
String goodsJson = "{\"name\":\"Huawei P100\",\"weight\":\"\",\"timestamp\":1622360677020}";

再次运行程序,报错:

Exception in thread "main" com.google.gson.JsonSyntaxException: java.lang.NumberFormatException: empty String
	at com.google.gson.internal.bind.TypeAdapters$7.read(TypeAdapters.java:228)
	at com.google.gson.internal.bind.TypeAdapters$7.read(TypeAdapters.java:218)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:131)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222)
	at com.google.gson.Gson.fromJson(Gson.java:932)
	at com.google.gson.Gson.fromJson(Gson.java:897)
	at com.google.gson.Gson.fromJson(Gson.java:846)
	at com.google.gson.Gson.fromJson(Gson.java:817)
	at com.example.lib._04_typeadapter.Test02.demo2(Test02.java:26)
	at com.example.lib._04_typeadapter.Test02.main(Test02.java:13)
Caused by: java.lang.NumberFormatException: empty String
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
	at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.lang.Double.parseDouble(Double.java:538)
	at com.google.gson.stream.JsonReader.nextInt(JsonReader.java:1202)
Caused by: java.lang.NumberFormatException: empty String

这里就可以使用 JsonDeserializer 来解决这个异常。修改代码如下:

JsonDeserializer<Integer> jsonDeserializer = new JsonDeserializer<Integer>() {
    @Override
    public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        try {
            return json.getAsInt();
        } catch (Exception exception) {
            return 0;
        }
    }
};
Gson gson = new GsonBuilder().registerTypeAdapter(int.class, jsonDeserializer).create();

修改完程序后,运行一下:

Goods{name='Huawei P100', weight=0, timestamp=1622360677020}

可以看到,这里解决了异常。

需要说明的是,这里面 registerTypeAdapter() 方法的第一个参数传递 int.class,对应着 Goods 类的 int 类型的字段;如果 Goods 类的字段类型为 Integer,那么 registerTypeAdapter 方法的第一个参数需要传递 Integer.class

使用 InstanceCreator 自定义反序列化

需求是有个 ShopperContext 类:

public class ShopperContext {
    public String userid;
    public String username;
    public Context context;

    public ShopperContext(Context context) {
        this.context = context;
    }
}

要求在序列化后,ShopperContext 对象里包含 Context 类型字段的值。

先不想太多,用之前的办法直接进行反序列化:

String json = "{\"userid\":\"id_12345\",\"username\":\"willwaywang6\"}";
Gson gson = new GsonBuilder().create();
ShopperContext shopperContext = gson.fromJson(json, ShopperContext.class);
System.out.println(shopperContext);

打印如下:

ShopperContext{userid='id_12345', username='willwaywang6', context=null}

可以看到,context 字段为 null,不满足需求。

这时就需要用到 InstanceCreator,修改代码如下:

// 模拟一个 Context 对象
final Context context = new Context();
InstanceCreator<ShopperContext> instanceCreator = new InstanceCreator<ShopperContext>() {
    @Override
    public ShopperContext createInstance(Type type) {
        return new ShopperContext(context);
    }
};
Gson gson = new GsonBuilder().registerTypeAdapter(ShopperContext.class, instanceCreator).create();

再次运行程序,查看结果:

ShopperContext{userid='id_12345', username='willwaywang6', context=com.example.lib._04_typeadapter.Context@6267c3bb}

再举一个案例:

有一个 ShopperSingleton 类:

public class ShopperSingleton {

    public String userid;
    public String username;

    private static ShopperSingleton singleton = new ShopperSingleton();

    private ShopperSingleton() {

    }

    public static ShopperSingleton getInstance() {
        return singleton;
    }

    @Override
    public String toString() {
        return "ShopperSingleton@" + hashCode() + "{" +
                "userid='" + userid + '\'' +
                ", username='" + username + '\'' +
                '}';
    }
}

这是一个单例类。

要求在反序列化后,把值赋值给这个单例的字段,而不是每次都创建新的实例。

还是采取之前的办法,先把代码码起来:

String json = "{\"userid\":\"id_12345\",\"username\":\"willwaywang6\"}";
Gson gson = new GsonBuilder().create();
// 在反序列化之前的打印
System.out.println(ShopperSingleton.getInstance());
ShopperSingleton shopperContext = gson.fromJson(json, ShopperSingleton.class);
// 在反序列化之后的打印
System.out.println(shopperContext);

打印如下:

ShopperSingleton@1066516207{userid='null', username='null'}
ShopperSingleton@777874839{userid='id_12345', username='willwaywang6'}

不符合需要,序列化前后,ShopperSingleton 的单例被打破了。

使用 InstanceCreator 来解决:

InstanceCreator<ShopperSingleton> instanceCreator = new InstanceCreator<ShopperSingleton>() {
    @Override
    public ShopperSingleton createInstance(Type type) {
        return ShopperSingleton.getInstance();
    }
};
Gson gson = new GsonBuilder().registerTypeAdapter(ShopperSingleton.class, instanceCreator).create();
ShopperSingleton shopperContext = gson.fromJson(json, ShopperSingleton.class);

打印如下:

ShopperSingleton@705927765{userid='null', username='null'}
ShopperSingleton@705927765{userid='id_12345', username='willwaywang6'}

可以看到,序列化前后都是一个对象,满足要求。

使用TypeAdapter 自定义序列化与反序列化

需求是这样的:
对于 Point 类:

public class Point {
    public int x;
    public int y;
}

序列化时为 “1,1” 这样的 json 字符串;反序列化时,“1,1” 可以转为 Point 对象。

直接看代码:

Gson gson = new GsonBuilder().registerTypeAdapter(Point.class, new TypeAdapter<Point>() {
    @Override
    public void write(JsonWriter writer, Point value) throws IOException {
        if (value == null) {
            writer.nullValue();
            return;
        }
        String xy = value.x + "," + value.y;
        writer.value(xy);
    }
    @Override
    public Point read(JsonReader reader) throws IOException {
        if (reader.peek() == JsonToken.NULL) {
            reader.nextNull();
            return null;
        }
        String xy = reader.nextString();
        String[] parts = xy.split(",");
        int x = Integer.parseInt(parts[0]);
        int y = Integer.parseInt(parts[1]);
        return new Point(x, y);
    }
}).create();
// 序列化
Point point = new Point(1, 1);
System.out.println(gson.toJson(point));
// 反序列化
// 特别注意:是 "\"2,2\"", 而不是 "2,2",否则反序列化会失败
String json = "\"2,2\"";
Point p = gson.fromJson(json, Point.class);
System.out.println(p);

2.5 源码里的解析过程

不管是序列化还是反序列化,都是分为三步:

  1. 获取 TypeToken 对象;
  2. 根据 TypeToken 对象获取 TypeAdapter对象;
  3. 通过 TypeAdapter 对象的 read() 方法进行反序列化,write() 方法进行序列化。

下面以序列化过程为例进行说明。

进行分析的代码比较简洁,如下:

Gson gson = new Gson();
// 序列化
Person person = new Person("willwaywang6", 18);
String personJson = gson.toJson(person);
System.out.println(personJson);

现在开始一起看源码:

toJson 有一系列的重载方法,最后调用的是这个重载方法:

public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOException {
  TypeAdapter<?> adapter = getAdapter(TypeToken.get(typeOfSrc));
  ((TypeAdapter<Object>) adapter).write(writer, src);
}

首先,通过 TypeToken.get(typeOfSrc) 获取到 TypeToken 对象传递给 getAdapter() 方法;

接着,看 getAdapter() 方法:

final List<TypeAdapterFactory> factories;
// typeTokenCache 是以 TypeToken 为键,以 TypeAdapter 为值的一个缓存集合对象
private final Map<TypeToken<?>, TypeAdapter<?>> typeTokenCache = new ConcurrentHashMap<TypeToken<?>, TypeAdapter<?>>();

public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
  TypeAdapter<?> cached = typeTokenCache.get(type == null ? NULL_KEY_SURROGATE : type);
  // 1,先从缓存里面取
  if (cached != null) {
    return (TypeAdapter<T>) cached;
  }
  // 2,没有缓存,构建一个
  for (TypeAdapterFactory factory : factories) {
    TypeAdapter<T> candidate = factory.create(this, type);
    if (candidate != null) {
      typeTokenCache.put(type, candidate);
        return candidate;
      }
  }
  throw new IllegalArgumentException("GSON (" + GsonBuildConfig.VERSION + ") cannot handle " + type);
}

可以看到,先从缓存里面取;没有的话,再遍历 factories 列表,如果可以构建一个非空的 TypeAdapter 对象,就返回。

目前有两个问题:

  • factories 列表是在哪里赋值的?
  • TypeAdapter 对象是如何构建出来的?

查看源码可以知道,factories 列表是在创建 Gson 对象(Gson gson = new Gson();)的时候赋值的。

Gson(Excluder excluder, FieldNamingStrategy fieldNamingStrategy,
    Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
    boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
    boolean prettyPrinting, boolean lenient, boolean serializeSpecialFloatingPointValues,
    LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle,
    int timeStyle, List<TypeAdapterFactory> builderFactories,
    List<TypeAdapterFactory> builderHierarchyFactories,
    List<TypeAdapterFactory> factoriesToBeAdded) {
 // 省略与分析无关的代码
  List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();
  // built-in type adapters that cannot be overridden
  factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
  factories.add(ObjectTypeAdapter.FACTORY);
  // the excluder must precede all adapters that handle user-defined types
  factories.add(excluder);
  // users' type adapters
  factories.addAll(factoriesToBeAdded);
  // type adapters for basic platform types
  factories.add(TypeAdapters.STRING_FACTORY);
  factories.add(TypeAdapters.INTEGER_FACTORY);
  factories.add(TypeAdapters.BOOLEAN_FACTORY);
  factories.add(TypeAdapters.BYTE_FACTORY);
  factories.add(TypeAdapters.SHORT_FACTORY);
  TypeAdapter<Number> longAdapter = longAdapter(longSerializationPolicy);
  factories.add(TypeAdapters.newFactory(long.class, Long.class, longAdapter));
  factories.add(TypeAdapters.newFactory(double.class, Double.class,
          doubleAdapter(serializeSpecialFloatingPointValues)));
  factories.add(TypeAdapters.newFactory(float.class, Float.class,
          floatAdapter(serializeSpecialFloatingPointValues)));
  factories.add(TypeAdapters.NUMBER_FACTORY);
  factories.add(TypeAdapters.ATOMIC_INTEGER_FACTORY);
  factories.add(TypeAdapters.ATOMIC_BOOLEAN_FACTORY);
  factories.add(TypeAdapters.newFactory(AtomicLong.class, atomicLongAdapter(longAdapter)));
  factories.add(TypeAdapters.newFactory(AtomicLongArray.class, atomicLongArrayAdapter(longAdapter)));
  factories.add(TypeAdapters.ATOMIC_INTEGER_ARRAY_FACTORY);
  factories.add(TypeAdapters.CHARACTER_FACTORY);
  factories.add(TypeAdapters.STRING_BUILDER_FACTORY);
  factories.add(TypeAdapters.STRING_BUFFER_FACTORY);
  factories.add(TypeAdapters.newFactory(BigDecimal.class, TypeAdapters.BIG_DECIMAL));
  factories.add(TypeAdapters.newFactory(BigInteger.class, TypeAdapters.BIG_INTEGER));
  factories.add(TypeAdapters.URL_FACTORY);
  factories.add(TypeAdapters.URI_FACTORY);
  factories.add(TypeAdapters.UUID_FACTORY);
  factories.add(TypeAdapters.CURRENCY_FACTORY);
  factories.add(TypeAdapters.LOCALE_FACTORY);
  factories.add(TypeAdapters.INET_ADDRESS_FACTORY);
  factories.add(TypeAdapters.BIT_SET_FACTORY);
  factories.add(DateTypeAdapter.FACTORY);
  factories.add(TypeAdapters.CALENDAR_FACTORY);
  factories.add(TimeTypeAdapter.FACTORY);
  factories.add(SqlDateTypeAdapter.FACTORY);
  factories.add(TypeAdapters.TIMESTAMP_FACTORY);
  factories.add(ArrayTypeAdapter.FACTORY);
  factories.add(TypeAdapters.CLASS_FACTORY);
  // type adapters for composite and user-defined types
  factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
  factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));
  this.jsonAdapterFactory = new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor);
  factories.add(jsonAdapterFactory);
  factories.add(TypeAdapters.ENUM_FACTORY);
  factories.add(new ReflectiveTypeAdapterFactory(
      constructorConstructor, fieldNamingStrategy, excluder, jsonAdapterFactory));
  this.factories = Collections.unmodifiableList(factories);
}

需要注意的是,向局部变量 factories 列表里面添加 TypeAdapterFactory 对象的顺序:

  1. 内置的 TypeAdapterFactory,不可以重写;
  2. 排除器(Excluder implements TypeAdapterFactory);
  3. 用户自定义的 TypeAdapterFactory
  4. 基本的平台类型的 TypeAdapterFactory
  5. type adapters for composite and user-defined types:这里面的 ReflectiveTypeAdapterFactory 是本次会分析到的,Person 对象的解析正是用到这个TypeAdapterFactory来创建 TypeAdapter 对象的。

回到我们分析的代码里面:

for (TypeAdapterFactory factory : factories) {
  TypeAdapter<T> candidate = factory.create(this, type);
  if (candidate != null) {
    typeTokenCache.put(type, candidate);
      return candidate;
  }
}

通过 factories 列表进行遍历,得到第一个不为 nullTypeAdapter 对象,直接返回就可以了。

那么,我们怎么知道本次分析获取的 TypeAdapter 对象是由哪个 TypeAdapterFactory 创建的呢?

还是通过打断点的方式:
在这里插入图片描述
在这里插入图片描述
可以看到我们获取的 TypeAdapterReflectiveTypeAdapterFactory.Adapter 对象,是通过 ReflectiveTypeAdapterFactory 创建的。

这里想插播一下,Gson 如何使用工厂方法模式,来控制 TypeAdapter 的创建。我们以 TypeAdapters 类的 newFactory() 方法来说明一下,之前向 factories 列表添加的 TypeAdapterFactory 对象很多是通过这个方法来创建的:

public static final TypeAdapterFactory STRING_FACTORY = newFactory(String.class, STRING);

public static <TT> TypeAdapterFactory newFactory(
    final Class<TT> type, final TypeAdapter<TT> typeAdapter) {
  return new TypeAdapterFactory() {
    @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
    @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
      return typeToken.getRawType() == type ? (TypeAdapter<T>) typeAdapter : null;
    }
    @Override public String toString() {
      return "Factory[type=" + type.getName() + ",adapter=" + typeAdapter + "]";
    }
  };
}

通过这个 TypeAdapterFactory 对象的 create 方法,来获取 TypeAdapter 对象。

@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
  return typeToken.getRawType() == type ? (TypeAdapter<T>) typeAdapter : null;
}

如果传入的 TypeToken 对象的原始类型是 String.class,那么就返回 TypeAdapter<String> STRING,否则就返回 null

回到分析的问题,现在我们知道本次分析的例子正是通过 ReflectiveTypeAdapterFactory 创建出的 TypeAdapter 对象来完成解析任务的。

但是,我们并没有告知 ReflectiveTypeAdapterFactoryPerson 类有哪些字段,字段的类型是什么,它是如何完成解析任务的呢?

为了搞清楚这些问题,需要去看一下 ReflectiveTypeAdapterFactory 的代码。

这里为了说明这一问题,对 ReflectiveTypeAdapterFactory 的代码部分,去掉了排除器的部分,去掉了字段命名策略的部分,去掉了 jsonAdapterFactory 的部分,修改了源代码里面的类嵌套的代码结构。

下面看一下精简后的代码吧,觉得还是比较清晰的:

MyReflectiveTypeAdapterFactory 类,用于创建 ReflectiveTypeAdapter 对象:

public final class MyReflectiveTypeAdapterFactory implements TypeAdapterFactory {
    private final ConstructorConstructor constructorConstructor;
    private final ReflectionAccessor accessor = ReflectionAccessor.getInstance();

    public MyReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor) {
        this.constructorConstructor = constructorConstructor;
    }

    @Override
    public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
        Class<? super T> raw = type.getRawType();
        if (!Object.class.isAssignableFrom(raw)) {
            return null; // it's a primitive!
        }
        ObjectConstructor<T> constructor = constructorConstructor.get(type);
        return new ReflectiveTypeAdapter<T>(constructor, getBoundFields(gson, type, raw));
    }
	// 收集以字段名为键,BoundField 为值的集合
    private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {
        Map<String, BoundField> result = new LinkedHashMap<>();
        if (raw.isInterface()) {
            return result;
        }
        while (raw != Object.class) {
            Field[] fields = raw.getDeclaredFields();
            for (Field field : fields) {
                accessor.makeAccessible(field);
                Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
                    String name = field.getName();
                    BoundField boundField = createBoundField(context, field, name,
                            TypeToken.get(fieldType));
                    result.put(name, boundField);
            }
            type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
            raw = type.getRawType();
        }
        return result;
    }
	// 创建 BoundField 对象,实现了字段的解析接口
    private BoundField createBoundField(
            final Gson context, final Field field, final String name,
            final TypeToken<?> fieldType) {
        final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
        final TypeAdapter<?> typeAdapter = context.getAdapter(fieldType);
        return new BoundField(name) {
            @SuppressWarnings({"unchecked", "rawtypes"})
            @Override
            void write(JsonWriter writer, Object value)
                    throws IOException, IllegalAccessException {
                Object fieldValue = field.get(value);
                TypeAdapter t = new TypeAdapterRuntimeTypeWrapper(context, typeAdapter, fieldType.getType());
                t.write(writer, fieldValue);
            }

            @Override
            void read(JsonReader reader, Object value)
                    throws IOException, IllegalAccessException {
                Object fieldValue = typeAdapter.read(reader);
                if (fieldValue != null || !isPrimitive) {
                    field.set(value, fieldValue);
                }
            }

            @Override
            public boolean writeField(Object value) throws IOException, IllegalAccessException {
                Object fieldValue = field.get(value);
                return fieldValue != value; // avoid recursion for example for Throwable.cause
            }
        };
    }
}

BoundField 类,抽象类,定义了字段的解析接口:

abstract class BoundField {
    final String name;

    protected BoundField(String name) {
        this.name = name;
    }

    abstract boolean writeField(Object value) throws IOException, IllegalAccessException;

    abstract void write(JsonWriter writer, Object value) throws IOException, IllegalAccessException;

    abstract void read(JsonReader reader, Object value) throws IOException, IllegalAccessException;
}

ReflectiveTypeAdapter 类,把解析的任务都交给了 BoundField 来完成:

public class ReflectiveTypeAdapter<T> extends TypeAdapter<T> {
    private final ObjectConstructor<T> constructor;
    private final Map<String, BoundField> boundFields;

    ReflectiveTypeAdapter(ObjectConstructor<T> constructor, Map<String, BoundField> boundFields) {
        this.constructor = constructor;
        this.boundFields = boundFields;
    }

    @Override
    public T read(JsonReader in) throws IOException {
        if (in.peek() == JsonToken.NULL) {
            in.nextNull();
            return null;
        }

        T instance = constructor.construct();

        try {
            in.beginObject();
            while (in.hasNext()) {
                String name = in.nextName();
                BoundField field = boundFields.get(name);
                if (field == null) {
                    in.skipValue();
                } else {
                    field.read(in, instance);
                }
            }
        } catch (IllegalStateException e) {
            throw new JsonSyntaxException(e);
        } catch (IllegalAccessException e) {
            throw new AssertionError(e);
        }
        in.endObject();
        return instance;
    }

    @Override
    public void write(JsonWriter out, T value) throws IOException {
        if (value == null) {
            out.nullValue();
            return;
        }
        out.beginObject();
        try {
            for (BoundField boundField : boundFields.values()) {
                if (boundField.writeField(value)) {
                    out.name(boundField.name);
                    boundField.write(out, value);
                }
            }
        } catch (IllegalAccessException e) {
            throw new AssertionError(e);
        }
        out.endObject();
    }
}

真正进行解析的任务是由 BoundField 来完成的,而创建 BoundField 和实现 BoundField 都使用到了反射。

3 最后

本文从基本的 Gson 使用开始,接着展示了 Gson 中常用的注解以及常用的配置,最后介绍了 registerAdapter 的使用。

还有一些未完成的内容:

  • registerTypeAdapter 方法与 registerTypeHierarchyAdapter 方法的区别;
  • 总结 Gson 框架里用到的设计模式:门面模式,适配器模式,策略模式,工厂模式(控制了到底是返回 TypeAdapter 的实例还是返回 null);
  • Gson 如何使用流:JsonReaderJsonWriter

代码传送门

希望能够帮助到大家。

参考