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
元素了,在 message
和 data
字段上分别添加注解,如下所示:
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}
显然没有反序列化成功。
解决办法仍是在 message
和 data
字段上分别添加注解,如下所示:
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 是不同的,分别为 content
,result
,result_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 | × | × |
参与序列化的字段有 firstName
、lastName
,因此序列化后的 json 字符串是:
{"firstName":"zhichao","lastName":"wang"}
参与反序列化的字段有 firstName
、age
,因此反序列化后的对象打印是:
Person{firstName='zhichao', lastName='null', age=18, password='null', phoneNumber='null'}
@Expose
与 transient
关键字的区别:
声明了 @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 源码里的解析过程
不管是序列化还是反序列化,都是分为三步:
- 获取
TypeToken
对象; - 根据
TypeToken
对象获取TypeAdapter
对象; - 通过
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
对象的顺序:
- 内置的
TypeAdapterFactory
,不可以重写; - 排除器(
Excluder implements TypeAdapterFactory
); - 用户自定义的
TypeAdapterFactory
; - 基本的平台类型的
TypeAdapterFactory
; - 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
列表进行遍历,得到第一个不为 null
的 TypeAdapter
对象,直接返回就可以了。
那么,我们怎么知道本次分析获取的 TypeAdapter
对象是由哪个 TypeAdapterFactory
创建的呢?
还是通过打断点的方式:
可以看到我们获取的 TypeAdapter
是 ReflectiveTypeAdapterFactory.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
对象来完成解析任务的。
但是,我们并没有告知 ReflectiveTypeAdapterFactory
,Person
类有哪些字段,字段的类型是什么,它是如何完成解析任务的呢?
为了搞清楚这些问题,需要去看一下 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 如何使用流:
JsonReader
,JsonWriter
?
希望能够帮助到大家。