今天开始学习 Google 的 Gson 库,Gson 是一个非常强大的库,可以将 JSON 格式的数据转化成 Java 对象,也支持将 Java 对象转成 JSON 数据格式。
先来看一些序列化例子,Gson 中的序列化意味着将 Java 对象映射成 JSON 数据格式,在接下来的教程中,我们会逐步介绍一些更复杂的情况,首先来看一个简单的例子:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserSimple {
private String name;
private String email;
private int age;
private boolean isDeveloper;
}
UserSimple 对象有四个变量:
Android 或 Java 应用程序需要将 UserSimple 对象转换为 JSON 表示,我们期望是这样的格式:
{
"name": "Ray",
"email": "ray@qq.com",
"age": 18,
"isDeveloper": true
}
来看下我们是如何用 Gson 转化的,首先要创建一个对象:
UserSimple userObject = new UserSimple("Ray", "ray@qq.com", 18, true);
需要用到 Gson 对象,使用以下构造函数来生成 Gson 对象:
Gson gson = new Gson();
使用 Gson 的 toJson() 方法并传入 UserSimple 对象作为参数:
String userJson = gson.toJson(userObject);
输出JSON格式数据
{"name":"Ray","email":"ray@qq.com","age":18,"isDeveloper":true}
Gson 中字符串被包装成 “”,整型没有被包装,对于单个对象映射使用 Gson 已经足够了。
引用上面已经输出的 JSON 格式数据,是最好不过的例子:
String userJson = "{\"name\":\"Ray\",\"email\":\"ray@qq.com\",\"age\":18,\"isDeveloper\":true}";
创建一个 Gson 对象,使用以下构造函数来生成 Gson 对象:
Gson gson = new Gson();
最后,我们通过 fromJson() 方法将JSON映射成一个 Java 对象:
UserSimple userObject = gson.fromJson(userJson, UserSimple.class);
注意第二个参数的传递,否则,Gson 不知道将 JSON 转换成什么类型。
我们希望通过实例来演示功能,所以让我们扩展 UserSimple Model 类,下面是扩展之前 UserSimple 类:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserSimple {
private String name;
private String email;
private int age;
private boolean isDeveloper;
}
现在我们将 UserAddress 类添加到 UserSimple 中:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserSimple {
private String name;
private String email;
private int age;
private boolean isDeveloper;
private UserAddress userAddress;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class UserAddress {
private String street;
private String houseNumber;
private String city;
private String country;
}
换句话说,UserSimple 类中包含了 UserAddress 对象,表示了用户和地址的一对一的关系,也就是嵌套对象。
在 UserSimple 类中,我们通过 UserAddress userAddress 字段来保留在当前类中的引用。
但是,在 JSON 中我们没有类或者引用,JSON 中惟一的判断标示是数据中不再使用 ID 绑定数据 ,Gson 中只能根据 "{}"
标志来创建一个新对象。
了解了基本的理论知识后,我们来尝试下使用 Gson 对 UserSimple 对象进行序列化操作。和之前一样,需要调用 Gson 对象的 tojson() 方法,如下:
UserSimple userObject = new UserSimple("Ray", "ray@qq.com", 18, true, new UserAddress("FS", "123", "GD", "China"));
Gson gson = new Gson();
String toJson = gson.toJson(userObject);
System.out.println(toJson);
输出结果
{"name":"Ray","email":"ray@qq.com","age":18,"isDeveloper":true,"userAddress":{"street":"FS","houseNumber":"123","city":"GD","country":"China"}}
与其他属性不同,像 age 等值都是有明确的取值,新的 userAddress 字段没有直接的值,相反,它又包含了一个新的子数据,被包含在一个 “{}” 中。类型和父节点中相同,根据 “{}” 来判断是否是一个新的对象。本类 UserSimple 中 userAddress 是一个嵌套对象。
根据结果我们看出 Gson 帮我们生成了我们想要的数据结构,并且也给嵌套的 userAddress 对象也创建了JSON 数据。当然 Gson 也是支持多层对象嵌套。
在平时的开发中,很多中情况是 API 接口返回 JSON 数据,我们解析成相应的对象。
{
"name": "Future Studio Steak House",
"owner": {
"name": "Christian",
"address": {
"city": "Magdeburg",
"country": "Germany",
"houseNumber": "42A",
"street": "Main Street"
}
},
"cook": {
"age": 18,
"name": "Marcus",
"salary": 1500
},
"waiter": {
"age": 18,
"name": "Norman",
"salary": 1000
}
}
为了反序列化 JSON 数据,我们先要创建相映匹配的 Java 类,推荐使用 IDEA 插件 [ GsonFormat ] 快速生成对应的JavaBean。
@ToString
@Data
public class Restaurant {
/**
* name : Future Studio Steak House
* owner : {"name":"Christian","address":{"city":"Magdeburg","country":"Germany","houseNumber":"42A","street":"Main Street"}}
* cook : {"age":18,"name":"Marcus","salary":1500}
* waiter : {"age":18,"name":"Norman","salary":1000}
*/
private String name;
private Owner owner;
private Cook cook;
private Waiter waiter;
@ToString
@Data
public static class Owner {
/**
* name : Christian
* address : {"city":"Magdeburg","country":"Germany","houseNumber":"42A","street":"Main Street"}
*/
private String name;
private Address address;
@ToString
@Data
public static class Address {
/**
* city : Magdeburg
* country : Germany
* houseNumber : 42A
* street : Main Street
*/
private String city;
private String country;
private String houseNumber;
private String street;
}
}
@ToString
@Data
public static class Cook {
/**
* age : 18
* name : Marcus
* salary : 1500
*/
private int age;
private String name;
private int salary;
}
@ToString
@Data
public static class Waiter {
/**
* age : 18
* name : Norman
* salary : 1000
*/
private int age;
private String name;
private int salary;
}
}
虽然可以这么写,但是我还是建议给每个嵌入类创建一个单独的类,防止以后接口数据更改导致我们客户端又要修改代码。
准备工作已完成,我们可以直接使用 Gson 来解析了,例如:
String restaurantJson = "{\"name\":\"Future Studio Steak House\",\"owner\":{\"name\":\"Christian\",\"address\":{\"city\":\"Magdeburg\",\"country\":\"Germany\",\"houseNumber\":\"42\",\"street\":\"Main Street\"}},\"cook\":{\"age\":18,\"name\":\"Marcus\",\"salary\":1500},\"waiter\":{\"age\":18,\"name\":\"Norman\",\"salary\":1000}}";
Gson gson = new Gson();
Restaurant restaurantObject = gson.fromJson(restaurantJson, Restaurant.class);
System.out.println(restaurantObject);
输出结果
Restaurant(name=Future Studio Steak House, owner=Restaurant.Owner(name=Christian, address=Restaurant.Owner.Address(city=Magdeburg, country=Germany, houseNumber=42, street=Main Street)), cook=Restaurant.Cook(age=18, name=Marcus, salary=1500), waiter=Restaurant.Waiter(age=18, name=Norman, salary=1000))
在我们介绍序列化之前,我们先来看下 Java 中的两种数据结构:Array 和 List。在 Java 中两者实现方式不同,使用哪一种数据类型取决于你的实际需求,但是在序列化这个问题上,Gson 并不关心这两种数据结构的具体实现。
在 JSON 数据格式中,不存在数组等结构,只是 Java 的具体实现使得这两种数据类型有很大不同。但是在上层它们表示出相同的结构。
接下来,我们为restaurant 添加一个 menu 属性,包含两个字段,restaurant 中的菜单可以理解成一个 restaurant 列表。如下:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RestaurantWithMenu {
private String name;
List<RestaurantMenuItem> menus;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class RestaurantMenuItem {
private String description;
private float price;
}
通过嵌套对象的方式即可,在 Java Model 中包含要映射变量的引用就可以了,要注意名字和JSON中字段名相同。
JSON 格式如下:
{
"name": "Future Studio Steak House",
"menus": [
...
]
}
与嵌套对象类似,我们没有 menus 的直接值,相反,JSON 中通过 “[]” 来包含一个对象,如上所述,在 JSON 数据中,数组和 List 结构是没有区别的。
menu 中包含一些对象,在我们那的 model 中,menus 只是其中的一个变量,我们先来手动生成一个完整的 JSON 数据。
通过下面这种方式,我们来模拟一个完整的 restaurant 数据:
List<RestaurantMenuItem> menus = new ArrayList<>();
menus.add(new RestaurantMenuItem("Spaghetti", 7.99f));
menus.add(new RestaurantMenuItem("Steak", 12.99f));
menus.add(new RestaurantMenuItem("Salad", 5.99f));
RestaurantWithMenu restaurant = new RestaurantWithMenu("Future Studio Steak House", menus);
Gson gson = new Gson();
String restaurantJson = gson.toJson(restaurant);
System.out.println(restaurantJson);
输出结果
{"name":"Future Studio Steak House","menus":[{"description":"Spaghetti","price":7.99},{"description":"Steak","price":12.99},{"description":"Salad","price":5.99}]}
如我们预料,我们得到了想要的数据,根据 “[]” 标志 List 开始,根据 “{}” 标志对象开始。
但是我们并不是总是将 List 嵌套在对象中,我们可能会直接得到一个 List,Gson 也是支持直接序列化一个 List。
List<RestaurantMenuItem> menus = new ArrayList<>();
menus.add(new RestaurantMenuItem("Spaghetti", 7.99f));
menus.add(new RestaurantMenuItem("Steak", 12.99f));
menus.add(new RestaurantMenuItem("Salad", 5.99f));
Gson gson = new Gson();
String menusJson = gson.toJson(menus);
System.out.println(menusJson);
输出结果
[{"description":"Spaghetti","price":7.99},{"description":"Steak","price":12.99},{"description":"Salad","price":5.99}]
JSON 中的 “[“ 表示一个对象列表开始,”{“ 表示一个对象开始了,我们应该记住 JSON 数据中格式差别。
来看一个列表最为根节点的例子:
[
{
"name": "Christian",
"flowerCount": 1
},
{
"name": "Marcus",
"flowerCount": 3
},
{
"name": "Norman",
"flowerCount": 2
}
]
根据之前介绍的,”[]”标示一个GSON 解析列表的开始和结束,我们还需要一个具体的 Java model类:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Founder {
private String name;
private int flowerCount;
}
接下来,我们将数据解析成我们想要的数据类型。
首先看解析成数组,通过 Gson 的 gson.fromJson 方法,我们很简单的将 GSON 解析成数组,注意这里传递的参数是 Founder[].class 而不是Founder.class ,如下:
String founderJson = "[{'name': 'Christian','flowerCount': 1}, {'name': 'Marcus', 'flowerCount': 3}, {'name': 'Norman', 'flowerCount': 2}]";
Gson gson = new Gson();
Founder[] founderArray = gson.fromJson(founderJson, Founder[].class);
System.out.println(founderArray.length); // 3
由于Java泛型的实现机制,使用了泛型的代码在运行期间相关的泛型参数的类型会被擦除,我们无法在运行期间获知泛型参数的具体类型(所有的泛型类型在运行时都是Object类型)。
实际开发中,我们更多的是转成一个 ArrayList,但是,我们不能像解析数组那样传入 List,为了让Gson知道我们要解析的数据类型,我们必须传递给它一个Type,内部根据 TypeToken 来区分要解析的类型。
String founderJson = "[{'name': 'Christian','flowerCount': 1}, {'name': 'Marcus', 'flowerCount': 3}, {'name': 'Norman', 'flowerCount': 2}]";
Gson gson = new Gson();
// 方法一
Type founderListType1 = new TypeToken<ArrayList<Founder>>() {}.getType();
List<Founder> founderList1 = gson.fromJson(founderJson, founderListType1);
System.out.println("founderList1.size() = " + founderList1.size()); // 3
// 方法二
List<Founder> founderList2 = gson.fromJson(founderJson, new TypeToken<ArrayList<Founder>>() {}.getType());
System.out.println("founderList2.size() = " + founderList2.size()); // 3
我们现在有一个这样的数据:
{
"name": "Future Studio Dev Team",
"website": "https://futurestud.io",
"founders": [{
"name": "Christian",
"flowerCount": 1
}, {
"name": "Marcus",
"flowerCount": 3
}, {
"name": "Norman",
"flowerCount": 2
}]
}
我们需要创建一个用来对应的 Java Model 类:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GeneralInfo {
private String name;
private String website;
private List<Founder> founders;
}
列表中 Model 类中的一个好处就是,我们在使用 Gson 解析时不再需要传递 TypeToken,直接传入类即可。
String generalInfoJson = "{'name': 'Future Studio Dev Team', 'website': 'https://futurestud.io', 'founders': [{'name': 'Christian', 'flowerCount': 1 }, {'name': 'Marcus','flowerCount': 3 }, {'name': 'Norman','flowerCount': 2 }]}";
Gson gson = new Gson();
GeneralInfo generalInfoObject = gson.fromJson(generalInfoJson, GeneralInfo.class);
执行结果
GeneralInfo(name=Future Studio Dev Team, website=https://futurestud.io, founders=[Founder(name=Christian, flowerCount=1), Founder(name=Marcus, flowerCount=3), Founder(name=Norman, flowerCount=2)])
除了解析成一个 List,也可以解析为数组格式。
Java 中的 Map 是一个非常灵活的数据结构,被用在很多场景,使用 Java 可以自由的实现应用。
先来看一个例子,使用 HashMap 实现:
HashMap<String, List<String>> employees = new HashMap<>();
employees.put("A", Arrays.asList("AA", "AAA", "AAAA"));
employees.put("B", Arrays.asList("BB", "BBB", "BBBB"));
employees.put("C", Arrays.asList("CC", "CCC", "CCCC"));
Gson gson = new Gson();
String employeeJson = gson.toJson(employees);
输出结果
{"A":["AA","AAA","AAAA"],"B":["BB","BBB","BBBB"],"C":["CC","CCC","CCCC"]}
正如 Java Map 结构一样,每个 Key 对应这个一个集合。
以下是区分 JSON 数据类型的一些简单的方法:
这里我们需要创建一个map类型的TypeToken类型。
String json = "{\"A\":[\"AA\",\"AAA\",\"AAAA\"],\"B\":[\"BB\",\"BBB\",\"BBBB\"],\"C\":[\"CC\",\"CCC\",\"CCCC\"]}";
Gson gson = new Gson();
HashMap<String, List<String>> employees1 = gson.fromJson(json, new TypeToken<HashMap<String, List<String>>>() {}.getType());
System.out.println(employees1); // {A=[AA, AAA, AAAA], B=[BB, BBB, BBBB], C=[CC, CCC, CCCC]}
我比较喜欢使用这种形式创建 TypeToken 对象。
Java 集合中包含很多中数据结构,我们已经了解了 List 和 Map 的使用,这两种结构在 JSON 格式表现上是不一样的。今天我们来看下 Set 数据结构,HastSet 是一个保证值唯一的集合,在 Java 中有很多应用,不用担心,我们使用 Gson 可以轻松搞定 Set 的序列化。
先来看一个例子,使用 HashSet 实现
HashSet<String> users = new HashSet<>();
users.add("Ray");
users.add("Ray1");
users.add("Ray2");
users.add("Ray"); // 重复新增
Gson gson = new Gson();
String usersJson = gson.toJson(users);
System.out.println(usersJson); // ["Ray1","Ray","Ray2"]
我们发现 Set 的 JSON 结构和 List 的 JSON 结构是一致的,但是在 Java 内部实现是有很大不同的,所以我们要明白,JSON 是一种和实现无关的数据结构,能被用来方便的传递。
刚刚说过 List 和 Set 的 JSON 数据相同,Gson 可以帮我们转成其他数据类型,还是 List 中那个例子:
[
{
"name": "Christian",
"flowerCount": 1
},
{
"name": "Marcus",
"flowerCount": 3
},
{
"name": "Norman",
"flowerCount": 2
}
]
使用方法和之前映射 List 类型一致的 ,这里我们需要创建一个 Set 类型的 TypeToken类型,如下:
String founderJson = "[{'name': 'Christian','flowerCount': 1}, {'name': 'Marcus', 'flowerCount': 3}, {'name': 'Norman', 'flowerCount': 2}]";
Gson gson = new Gson();
HashSet<Founder> founderHashSet = gson.fromJson(founderJson, new TypeToken<HashSet<Founder>>() {}.getType());
System.out.println(founderHashSet); // [Founder(name=Norman, flowerCount=2), Founder(name=Christian, flowerCount=1), Founder(name=Marcus, flowerCount=3)]
我们来看一个有趣的问题,Gson中是如何处理 null 情况的?
我们使用之前的 model 类 UserSimple,不过name字段我们给赋值为null,如下:
UserSimple userObject = new UserSimple(null, "ray@qq.com", 18, true);
Gson gson = new Gson();
String userJson = gson.toJson(userObject);
System.out.println(userJson); // {"email":"ray@qq.com","age":18,"isDeveloper":true}
Gson 在序列化过程中会忽略空值,也就是说,如果没有赋值,JSON 中就不会出现该字段,如何你希望保留字段及空值,Gson 能帮我们实现(后面会讲到)。
现在我们只需要知道序列化会忽略 null。
将上面得到的 userJson 反序列化得到的结果:
UserSimple(name=null, email=ray@qq.com, age=18, isDeveloper=true)
我们看到只有和 JSON 字段匹配的才被映射成功。
再来看一个字段不全的例子:
{
"email": "ray@qq.com",
"name": "Ray"
}
输出结果
UserSimple(name=Ray, email=ray@qq.com, age=0, isDeveloper=false)
我们发现没有被映射成功的字段都被赋值为该类型的默认值,例如:age = 0,boolean 类型的 isDeveloper = false。
@SerializedName 是另一个非常实用的注解。@SerializedName 注解更改了自动匹配 JSON 字段的方式,平时开发中,我们总是默认保持 Java 类属性字段名和 JSON 的字段是一一对应,可有使用并不是总是这样的情况,也许你没有访问继承 Java 类或者你必须遵守公司的命名规则,这就需要使 @SerializedName 注解来匹配 Gson 字段,是一种非常优雅的方式。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserSimple {
private String name;
private String email;
private int age;
private boolean isDeveloper;
}
如果没有使用 @Expose
, UserSimple 类中的字段将都会被一一映射。
但是,假设 API 发生改变导致返回的 JSON 数据格式发生更改,接口返回了 “fullName” 而不是”name”。
{
"age": 18,
"email": "ray@qq.com",
"fullName": "Ray",
"isDeveloper": true
}
不用担心,我们不要改动任何代码,我们只需要给类中改变的字段添加注解即可。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserSimple {
@SerializedName("fullName")
private String name;
private String email;
private int age;
private boolean isDeveloper;
}
使用 Gson 进行注解,并在此运行,这就是是使用注解自动匹配的好处。
当然,你可以使用注解 @SerializedName 方式来保证命名规范,同时又可以正常映射接口字段,如果你的接口字段和命名规则差别很大,使用@SerializedName 注解来解决还是有必要的。
我们将介绍如何将多个字段名称映射到一个字段属性上。如果你的应用有多个接口数据,那么使用 @SerializedName 注解就可以解决,你仍然可以使用原来的model类,而不必增加新的model类。
如果你的服务器接口返回了不同的字段名,你可以使用 @SerializedName 注解来解决这个问题。
但是,还不能满足我们的需求,SerializedName 接受两个参数,value、alternate,第一个value是默认参数,如果你只填了一个参数,那这个值将会赋值给 value,第二个 alternate 就为空,同样都填上也是可以的,也可以使用 “ {} “ 传多个值,如果 json 中同时存在多个匹配的值,只会取 alternate 中最后一个匹配的值。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserSimple {
@SerializedName(value = "fullName", alternate = "userName")
private String name;
private String email;
private int age;
private boolean isDeveloper;
}
SerializedName 改变了默认序列化和默认反序列化的字段取值,所以,如果你使用 Gson 将 Java model 类生称 JSON 串,结果就是 value 的名称就是字段的名称。
alternate 属性是用在反序列化上的,也就是说从 JSON 到 Java 类的过程。Gson 会帮我们匹配 JSON 中的命名,并尝试找到一个字段映射。对于上面例子model类中,Gson 会将字段 fullName 或者 userName 映射到字符串 name上。无论哪一个匹配都会映射。
例如:
{
'fullName': 'Ray',
'email': 'ray@qq.com'
}
{
'userName': 'Ray',
'email': 'ray@qq.com'
}
再来看一个例子:
{
'username': 'Ray1',
'fullName': 'Ray2',
'email': 'ray@qq.com'
}
如果多个字段匹配一个变量字段,谁在后面 Gson 就会将值赋给谁,结果最终是 name:Ray2。
之前我们经常使用这样来生成一个 Gson 对象,Gson gson = new Gson();
这是标准的 Gson生成方式,不过 Gson 提供了扩展,如果你有一些特殊的需求,你可以使用 GsonBuider 来自定义 Gson。
// previously
Gson gson = new Gson();
// now using GsonBuilder
GsonBuilder gsonBuilder = new GsonBuilder();
Gson gson = gsonBuilder.create();
// or
Gson gson = new GsonBuilder().create();
使用 GsonBuilder.create() 方法就可以创建一个 Gson 实例,有了它就可以解析各种数据类型了。
首先要介绍下 GsonBuilder 中命名规则,我们一直认为 Java model 中字段名和 JSON 中字段名应该保持一致,但是我们可以通过 @SerializedName 来帮我们解决这种字段不一致的问题。
使用 @SerializedName 解决是一种方式,但是 Gson 也提供了一种自定义方式,使用 FieldNamingPolicy,我们根据例子来说明。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserNaming {
private String Name;
private String email_of_developer;
private boolean isDeveloper;
private int _age;
}
可以看到,我们给不同类型的变量命名了不同的规则的字段名,这样做主要是为了之后自定义设置。
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.IDENTITY).create();
先不用理解 FieldNamingPolicy.IDENTITY,这段代码的意思是,我们的 Gson 使用了 FieldNamingPolicy.IDENTITY 的自定义设置,接下来的几个例子我们将介绍 FieldNamingPolicy 中的其他属性。
FieldNamingPolicy.IDENTITY 的作用是,完全匹配我们 Java model 中的字段名,不管你有没有设置其他注解属性,我们使用以下代码来生成一段 JSON 数据:
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.IDENTITY).create();
UserNaming userNamingObject = new UserNaming("Ray", "qq@qq.com", true, 18);
String toJson = gson.toJson(userNamingObject);
输出结果
{"Name":"Ray","email_of_developer":"qq@qq.com","isDeveloper":true,"_age":18}
可以看到字段名就是我们 model 中的字段名,没有发生改变,如果你使用默认生成实例或者不使用 GsonBuilder 自定义生成,最后都会得到相同的结果。
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
UserNaming userNamingObject = new UserNaming("Ray", "qq@qq.com", true, 18);
String toJson = gson.toJson(userNamingObject);
输出结果
{"name":"Ray","email_of_developer":"qq@qq.com","is_developer":true,"_age":18}
使用 FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES 规则,将修改生成的 JSON 中的字段名,格式将全部变成小写,并且每个单词用“_” 分割。
查看源码
LOWER_CASE_WITH_UNDERSCORES {
public String translateName(Field f) {
return separateCamelCase(f.getName(), "_").toLowerCase(Locale.ENGLISH);
}
}
有了上面的理解,这个就很好理解了,除了每个单词用“-” 分隔意外其他和 FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES 都一样
输出结果
{"name":"Ray","email_of_developer":"qq@qq.com","is-developer":true,"_age":18}
这个就更简单了,规则是每个单词的第一个字母都要大写,其他不变
输出结果
{"Name":"Ray","Email_of_developer":"qq@qq.com","IsDeveloper":true,"_Age":18}
看结果就可以发现,采用了驼峰命名规则,_Age 看起来比较特殊,但它是正确的命名方法。
规则如下:
含有 “_” 的链接的不会在使用空格。
输出结果
{"Name":"Ray","Email_of_developer":"qq@qq.com","Is Developer":true,"_Age":18}
@SerializedName 可能不能满足你的需求规则,我们将使用 GsonBuilder的 setFieldNamingStrategy 方法 ,FieldNamingStrategy 自定义规则。例如:
//FieldNamingStrategy customPolicy = new FieldNamingStrategy() {
// @Override
// public String translateName(Field field) {
// return field.getName().replace("_", "");
// }
//};
FieldNamingStrategy customPolicy = field -> field.getName().replace("_", "");
Gson gson = new GsonBuilder().setFieldNamingStrategy(customPolicy).create();
UserNaming userNamingObject = new UserNaming("Ray", "qq@qq.com", true, 18);
String toJson = gson.toJson(userNamingObject);
输出结果
{"Name":"Ray","emailofdeveloper":"qq@qq.com","isDeveloper":true,"age":18}
上面将会把 “_” 剔除,需要注意的是一次只能有一个规则,如果使用多个规则,将以最后一个为准。
以上都是关于序列化的操作,统一以上的规则也使用于 JSON 的反序列化操作,如果你的服务器返回的 JSON 字段是以小写和下划线作为规则的话,就可以使用 LOWER_CASE_WITH_UNDERSCORES
规则来匹配字段名称。
例如:
{
"reviewer_name": "Ray"
}
可以这样定义 Java model 类:
public class PostReviewer {
private String reviewerName; //不用分隔线,采用驼峰命名
}
之前的例子中,空值的映射中,如果你的数据结构中没有给字段赋值或者设置 null,序列化的 JSON 中是不会出现该字段的。
这种通过忽略null值设定的好处就是减少了 JSON 输出的大小。但是我们有时候并不想这样,有一些需求我们必须将所有的字段都要显示出来,不管是赋值没赋值,空还是不为空。
幸运的是 Gson 提供了设置的方法,我们将使用 GsonBuilder 来定制我们的规则。
Gson gson = new GsonBuilder()
.serializeNulls() // 重点
.create();
UserSimple userObject = new UserSimple(null, "ray@qq.com", 18, true);
String usersJson = gson.toJson(userObject);
System.out.println(usersJson); // {"fullName":null,"email":"ray@qq.com","age":18,"isDeveloper":true}
使用默认的规则,fullName 字段没有被输出,如果你需要 fullName 被输出,只需要设置 GsonBuilder 的 serializeNulls()
即可。
@Expose 默认有两个属性:serialize 和 deserialize,默认值都为 true。
如果你给字段设置了 @Expose 注解,但是没有设置serialize 和 deserialize,那 model 中标记注解的字段都将会输出。
添加 @Expose 注解是一个非常简单的控制哪些要被(反)序列化的方式。我们建议如果需要所有的都被转化,就不用添加 @Expose 注解了,不然只会搞乱你的模型类。
该注解只有在你使用
GsonBuilder
去构造Gson时,同时调用excludeFieldsWithoutExposeAnnotation()
方法才起作用。 如果你使用new Gson()
实例化一个对象的话,那么@Expose
的注解是无效的,User中的参数都是会参与反序列化或序列化。
在 @Expose
注解上配置属性 serialize = false, deserialize = false
,代表这个 emailAddress
不参与序列化或者反序列化,其他正常显示。
@Data
public class User {
public String name;
public int age;
@Expose(serialize = false, deserialize = false)
public String emailAddress;
}
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
String json = "{\"name\":\"Ray\",\"age\":18,\"emailAddress\":\"zhangsan@ceshi.com\"}";
User user = gson.fromJson(json, User.class);
System.out.println(user); // User(name=张三, age=24, emailAddress=null)
注意:方法一在序列化的时候,输出的结果是 {}
同样的代码,只是 @Expose
注解没有配置属性,就是不忽略 emailAdress
属性,而 name
和 age
属性会被忽略。
@Data
public class User {
public String name;
public int age;
@Expose()
public String emailAddress;
}
输出结果
System.out.println(user); // User(emailAddress=zhangsan@ceshi.com)
注意:方法二在序列化的时候,输出的结果是 {“emailAddress”:”zhangsan@ceshi.com“}
如果使用 transient 来描述字段,将不能被序列化和反序列化
@Data
public class User {
public String name;
public int age;
public transient String emailAddress;
}
之前使用 @Expose 和 transient 来改变单个字段的序列化和反序列化规则。
@Data
public class UserDate {
// @Expose
private String _name;
private String email;
private boolean isDeveloper;
private int age;
private Date registerDate = new Date();
public UserDate(String _name, String email, boolean isDeveloper, int age) {
this._name = _name;
this.email = email;
this.isDeveloper = isDeveloper;
this.age = age;
}
}
接下来来看一种更通用的方式,Gson 提供的 ExclusionStrategies。
Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
return false;
}
@Override
public boolean shouldSkipClass(Class<?> aClass) {
return aClass == Date.class || aClass == boolean.class;
}
}).create();
UserDate userDate = new UserDate("Ray", "ray@qq.com", true, 18);
String usersJson = gson.toJson(userDate);
这个 ExclusionStrategy 包含两个可重写的方法,上面的规则只使用了 shouldSkipClass 这个方法,看代码就很容易理解,凡事遇到 Date.class 或者 boolean.class 都将被忽略。
输出结果
{"_name":"Ray","email":"ray@qq.com","age":18}
另一个方法 shouldSkipField 是用来忽略单个字段的,如果你想要忽略带有 “_” 的字段,可以这么写:
Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
return fieldAttributes.getName().contains("_");
}
@Override
public boolean shouldSkipClass(Class<?> aClass) {
return aClass == Date.class || aClass == boolean.class;
}
}).create();
UserDate userDate = new UserDate("Ray", "ray@qq.com", true, 18);
String usersJson = gson.toJson(userDate);
看代码就很容易理解,凡事遇到属性变量包含 "_"
都将被忽略,如 _name
。
输出结果
{"email":"ray@qq.com","age":18}
有了 ExclusionStrategies 我们可以根据自己的需求定制更多不同的规则,需要注意创建对应的 GsonBuilder,并正确使用方法。
UserDate 类中只是一些简单的数据类型,还有一些更复杂的情况,同样都可以使用 ExclusionStrategies 规则来帮我们实现,理论上 Gson 能帮我们忽略任何类。
上面的例子使用的是 setExclusionStrategies 方法,不管是序列化还是反序列化都会起作用,如果我们只想其中一个起作用,选择调下面的方法就行了:
用法和 ExclusionStrategy 的实现一样,可重写两个方法实现。
GsonBuilder 提供 excludeFieldsWithModifiers() 方法,让我们可以自定义可忽略类型,要用到 java.lang.reflect.Modifier 类。
@Data
public class UserModifier {
private String name;
private transient String email;
private static boolean isDeveloper;
private final int age = 0;
public UserModifier(String name, String email) {
this.name = name;
this.email = email;
}
}
如果你想忽略 final 和 static 类型的字段, 保留 transient 类型的字段,需要这么配置:
//Gson gson = new GsonBuilder().create(); // 使用前
Gson gson = new GsonBuilder().excludeFieldsWithModifiers(Modifier.STATIC, Modifier.FINAL).create();
UserModifier userModifier = new UserModifier("Ray", "ray@qq.com");
String userJson = gson.toJson(userModifier);
输出结果
{"name":"Ray","age":0}
-------------- 使用前后 --------------
{"name":"Ray","email":"ray@qq.com"}
达到这样的效果,是因为我们重新设置了 excludeFieldsWithModifiers() 方法的参数,Modifier.STATIC, Modifier.FINAL,根据这两个类型,就可以只忽略设置的类型,如果参数为空,将不起作用。
这个之前已经介绍了,可查看这篇文,需要注意的是手动设置 excludeFieldsWithoutExposeAnnotation() 方法才能生效。
我们知道 JSON 必须符合一定的标准才能被解析成功,在 JsonReader 的源码中我们看到 Gson 遵循的是 RFC4627 规则,本文将介绍下 Gson 和 JSON 规范的关系。
通常我们将 Java 对象序列化成 JSON 格式的数据时,并不会有什么太大的问题,此时的 JSON 将是一个标准的格式,重点是反序列化可能会有问题。
Gson 内部使用的是 JsonReader 类,看源码能发现里面有一个 lenient 的属性,默认是 false,也就是说默认值接受标准的 JSON 格式数据,如果数据有问题,将抛出异常解析失败。
JsonReader 也提供了设置 lenient 属性的方法,来忽略一些不标准的 JSON 数据格式。
不过我们建议还是要使用标准的 JSON 数据格式,除非你有特殊情况。
如果你开启了 lenient 方式,Gson 的只能帮我们忽略掉以上的格式错误,如果你的 JSON 中有其他错误,Gson 将会抛出 MalformedJsonException 异常,这样你必须要检查下你的 JSON 数据的合法性。
这两种类型是 Java 中常见的类型,用来表示一些特定类型的值,但是在 JSON 中并没有这些类型。
让我们引用 Gson 中关于这个问题的解释:
JSON 规范的第2.4节不允许特殊的double值(NaN,Infinity,-Infinity),但是,Javascript规范(见第4.3.20,4.3.22,4.3.23节)允许这些值作为有效的 Javascript 值。 此外,大多数 JavaScript 引擎将接受 JSON 中的这些特殊值,而没有问题。 因此,在实际应用中,即使不能作为 JSON 规范,但是接受这些值作为有效的 JSON 是有意义的。
如果你 Java 对象中包含一个正常的 Floats 或者 Doubles 类型的数据,是可以正常序列化得到 JSON的,如果你传入 Float.POSITIVE_INFINITY 值,Gson 将会抛出异常,因为这个值是不能符合 JSON 标准的。
UserFloat userFloat = new UserFloat("Norman", Float.POSITIVE_INFINITY);
Gson gson = new Gson();
String toJson = gson.toJson(userFloat);
输出结果【异常】
Exception in thread "main" java.lang.IllegalArgumentException: Infinity is not a valid double value as per JSON specification. To override this behavior, use GsonBuilder.serializeSpecialFloatingPointValues() method.
at com.google.gson.Gson.checkValidFloatingPoint(Gson.java:359)
at com.google.gson.Gson$2.write(Gson.java:351)
at com.google.gson.Gson$2.write(Gson.java:337)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:127)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:245)
at com.google.gson.Gson.toJson(Gson.java:704)
at com.google.gson.Gson.toJson(Gson.java:683)
at com.google.gson.Gson.toJson(Gson.java:638)
at com.google.gson.Gson.toJson(Gson.java:618)
解决的办法就是通过 GsonBuilder 设置 serializeSpecialFloatingPointValues() 方法
UserFloat userFloat = new UserFloat("Norman", Float.POSITIVE_INFINITY);
Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
String toJson = gson.toJson(userFloat);
执行结果
{"name":"Norman","weight":Infinity}
注意:有了这个方法,并不意味着你可以随意的配置 JSON 数据格式,建议还是要遵循标准的 JSON 规范。
枚举在 Java 中使用非常普遍,可以使用它来规范一些常量的取值。但对于 JSON 来说,同样是不存在枚举类型,如果在解析中使用了枚举,将会发生什么?
先来定义一个枚举 Day:
public enum Day {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
在创建一个 Java model 类:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDayEnum {
private String _name;
private String email;
private boolean isDeveloper;
private int age;
private Day day = Day.FRIDAY;
}
我们使用 Gson 来序列化 UserDayEnum 对象:
UserDayEnum userDayEnum = new UserDayEnum("Ray", "ray@qq.com", true, 18, Day.SUNDAY);
Gson gson = new Gson();
String userJson = gson.toJson(userDayEnum);
输出结果
{"_name":"Ray","email":"ray@qq.com","isDeveloper":true,"age":18,"day":"SUNDAY"}
根据结果我们看到,不用做任何配置处理,Gson 就帮我们正常输出了 JSON 格式的数据。
反序列化也非常简单,同样不做任何额外的配置:
UserDayEnum userDayEnum = new UserDayEnum("Ray", "ray@qq.com", true, 18, Day.SUNDAY);
Gson gson = new Gson();
String userJson = gson.toJson(userDayEnum);
UserDayEnum userDayEnum1 = gson.fromJson(userJson, UserDayEnum.class);
输出结果
UserDayEnum(_name=Ray, email=ray@qq.com, isDeveloper=true, age=18, day=SUNDAY)
使用 @SerializedName 注解给每个变量赋予另一个字段名:
public enum Day2 {
@SerializedName("1")
MONDAY,
@SerializedName("2")
TUESDAY,
@SerializedName("3")
WEDNESDAY,
@SerializedName("4")
THURSDAY,
@SerializedName("5")
FRIDAY,
@SerializedName("6")
SATURDAY,
@SerializedName("7")
SUNDAY
}
我们将使用新的 JSON 数据来(反)序列化:
UserDayEnum2 userDayEnum = new UserDayEnum2("Ray", "ray@qq.com", true, 18, Day2.MONDAY);
Gson gson = new Gson();
String userJson = gson.toJson(userDayEnum);
UserDayEnum2 userDayEnum2 = gson.fromJson(userJson, UserDayEnum2.class);
输出结果
{"_name":"Ray","email":"ray@qq.com","isDeveloper":true,"age":18,"day2":"1"}
-------------- (反)序列化 --------------
UserDayEnum2(_name=Ray, email=ray@qq.com, isDeveloper=true, age=18, day2=MONDAY)
通过结果可以看到枚举的(反)序列化使用,并配合 @SerializedName 来简化使用。
之前使用 Gson 来解析 Java 对象,我们必须传入要解析的 Java class 类型,先来看例子。
Gson gson = new Gson();
List<Integer> integerList = new ArrayList<>();
integerList.add(1);
integerList.add(2);
integerList.add(3);
List<String> stringList = new ArrayList<>();
stringList.add("1");
stringList.add("2");
stringList.add("3");
String integerJson = gson.toJson(integerList, new TypeToken<List<Integer>>() {}.getType());
String stringJson = gson.toJson(stringList, new TypeToken<List<String>>() {}.getType());
输出结果
[1,2,3] // integerJson
["1","2","3"] // stringJson
再来看一个泛型封装的例子 Box,只包含了一个泛型对象:
@Data
public class Box<T> {
private T boxContent;
public Box(T boxContent) {
this.boxContent = boxContent;
}
}
Gson gson = new Gson();
Box<String> stringBox = new Box<>("String Type");
Box<Integer> integerBox = new Box<>(42);
Box<UserDate> userDateBox = new Box<>(new UserDate("Ray", "ray@qq.com", true, 18));
String stringJson = gson.toJson(stringBox, new TypeToken<Box<String>>() {}.getType());
String integerJson = gson.toJson(integerBox, new TypeToken<Box<Integer>>() {}.getType());
String userDateJson = gson.toJson(userDateBox, new TypeToken<Box<UserDate>>() {}.getType());
System.out.println(stringJson);
System.out.println(integerJson);
System.out.println(userDateJson);
输出结果
{"boxContent":"String Type"}
{"boxContent":42}
{"boxContent":{"_name":"Ray","email":"ray@qq.com","isDeveloper":true,"age":18,"registerDate":"Feb 28, 2020 10:12:52 PM"}}
假设有一段这样的 JSON 数据,我们使用 Box 泛型来解析。
{
"boxContent": {
"_name": "Norman",
"age": 26,
"email": "norman@fs.io",
"isDeveloper": true,
"registerDate": "Jun 7, 2016 7:15:29 AM"
}
}
首先我们要明确 JSON 属于 Box 中泛型的哪种类型,知道了泛型的类型,我们就能确认 TypeToken 的类型。
String complexGenericJson = "{\"boxContent\":{\"_name\":\"Norman\",\"age\":26,\"email\":\"norman@fs.io\",\"isDeveloper\":true,\"registerDate\":\"Jun 7, 2016 7:15:29 AM\"}}";
Gson gson = new Gson();
Box boxWithData = gson.fromJson(complexGenericJson, new TypeToken<Box<UserDate>>() {}.getType());
Box<UserDate> boxWithoutData = gson.fromJson(complexGenericJson, Box.class);
输出结果
boxWithData = Box(boxContent=UserDate(_name=Norman, email=norman@fs.io, isDeveloper=true, age=26, registerDate=Tue Jun 07 07:15:29 CST 2016))
boxWithoutData = Box(boxContent={_name=Norman, age=26.0, email=norman@fs.io, isDeveloper=true, registerDate=Jun 7, 2016 7:15:29 AM})
解析泛型的关键就是我们要知道最终要解析成那种泛型类型。
本文将实现一个自定义的 Gson serialization 过程,有些情况我们可能会考虑到自定义,例如:和 server 通信时,有时候不需要传递一个完成 JSON 信息,只需要关键信息即可,接下来我们一步一步分析。
有个这样的场景:App 从 服务器获取一个列表,用户可以订阅列表中的每一项,但是要将订阅的某一条发送给服务器同步。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Merchant {
private int id;
private String name;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserSubscription {
private String name;
private String email;
private int age;
private boolean isDeveloper;
List<Merchant> merchantList;
}
序列化
Merchant merchant1 = new Merchant(1, "Future Studio");
Merchant merchant2 = new Merchant(2, "Coffee Shop");
List<Merchant> merchantList = Arrays.asList(merchant1, merchant2);
UserSubscription userSubscriptionObject = new UserSubscription("Ray", "ray@qq.com", 18, true, merchantList);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String userSubscriptionToJson = gson.toJson(userSubscriptionObject);
输出结果
{
"name": "Ray",
"email": "ray@qq.com",
"age": 18,
"isDeveloper": true,
"merchantList": [
{
"id": 1,
"name": "Future Studio"
},
{
"id": 2,
"name": "Coffee Shop"
}
]
}
这是一个非常标准 JSON 的输出,但是在实际中,不可能是这么小的数据量,而且 Merchant 结构可能非常复杂,这就会造成 JSON 数据非常大,从而导致解析或传递耗时。
我们第一个想到的方法是减少 Merchant 中不必要字段的序列化,使用之前学过的 @Expose 来简化 JSON,我们来调整下 Merchant:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Merchant {
@Expose
private int id;
@Expose(serialize = false)
private String name;
}
UserSubscription 也需要调整
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserSubscription {
@Expose
private String name;
@Expose
private String email;
@Expose
private int age;
@Expose
private boolean isDeveloper;
@Expose
List<Merchant> merchantList;
}
修改 Gson 配置
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.excludeFieldsWithoutExposeAnnotation() // @Expose 生效
.create();
输出结果
{
"name": "Ray",
"email": "ray@qq.com",
"age": 18,
"isDeveloper": true,
"merchantList": [
{
"id": 1
},
{
"id": 2
}
]
}
从结果上看已经减少输出了 name 字段,看起来已经优化了,在属性较多的时候改动是非常大的(不推荐)。但是问题又来了,如果其他接口,需要一个完整的 JSON 结果,这该怎么办?
通过 @Expose 是能解决一部分问题,但是存在局限性,现在我们使用自定义来解决这些问题,作法不干涉 Merchant 类,只在干涉序列化过程。
为了实现自定义需求,我们需要使用 GsonBuilder 来帮我们生成 Gson 实例,需要给 Merchant 类注册一个 adapter:
Merchant merchant1 = new Merchant(1, "Future Studio");
Merchant merchant2 = new Merchant(2, "Coffee Shop");
List<Merchant> merchantList = Arrays.asList(merchant1, merchant2);
UserSubscription userSubscriptionObject = new UserSubscription("Ray", "ray@qq.com", 18, true, merchantList);
// JsonSerializer 实现
JsonSerializer<Merchant> serializer = new JsonSerializer<Merchant>() {
@Override
public JsonElement serialize(Merchant merchant, Type type, JsonSerializationContext jsonSerializationContext) {
JsonObject jsonMerchant = new JsonObject();
jsonMerchant.addProperty("id", merchant.getId());
return jsonMerchant;
}
};
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(Merchant.class, serializer) // 注册
.create();
String userSubscriptionToJson = gson.toJson(userSubscriptionObject);
大部分都还是之前介绍过的方法,唯一陌生的是 registerTypeAdapter() 方法,需要传入两个参数,第一个序列化对象类型,第二个是 JsonSerializer 接口的具体实现。
JsonSerializer 实现只有一个 serialize 方法,我们重写次方法,在方法内添加我们处理,这里使用 JsonObject 只添加 src 的 id 字段。
根据自己的需求选择调用响应的方法,再次输出:
{
"name": "Ray",
"email": "ray@qq.com",
"age": 18,
"isDeveloper": true,
"merchantList": [
{
"id": 1
},
{
"id": 2
}
]
}
可以看到上面的结果和使用 @Expose 的结果是一样的。
和自定义序列化对象类似,我们只需要更改下 JsonSerializer 中的类型为 List 即可:
Merchant merchant1 = new Merchant(1, "Future Studio");
Merchant merchant2 = new Merchant(2, "Coffee Shop");
List<Merchant> merchantList = Arrays.asList(merchant1, merchant2);
UserSubscription userSubscriptionObject = new UserSubscription("Ray", "ray@qq.com", 18, true, merchantList);
// JsonSerializer 实现
JsonSerializer<List<Merchant>> serializer = new JsonSerializer<List<Merchant>>() {
@Override
public JsonElement serialize(List<Merchant> merchants, Type type, JsonSerializationContext jsonSerializationContext) {
JsonObject jsonMerchant = new JsonObject();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < merchants.size(); i++) {
if (i+1 != merchants.size()) {
builder.append(merchants.get(i).getId() + ",");
} else {
builder.append(merchants.get(i).getId());
}
}
jsonMerchant.addProperty("ids", builder.toString());
return jsonMerchant;
}
};
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(new TypeToken<List<Merchant>>() {}.getType(), serializer) // 注册
.create();
String userSubscriptionToJson = gson.toJson(userSubscriptionObject);
输出结果
{
"name": "Ray",
"email": "ray@qq.com",
"age": 18,
"isDeveloper": true,
"merchantList": {
"ids": "1,2"
}
}
我们可以看到,merchantList 中,大大减少了输出,这对于客户端和服务器都是有利的,有利于提升访问速度和性能。但是 merchantList 中的数据格式有些问题。
针对上一节的问题,我们只需要修改 serialize方法中的实现即可,不在使用 JsonObject,而是使用 JsonArray,实现如下:
Merchant merchant1 = new Merchant(1, "Future Studio");
Merchant merchant2 = new Merchant(2, "Coffee Shop");
List<Merchant> merchantList = Arrays.asList(merchant1, merchant2);
UserSubscription userSubscriptionObject = new UserSubscription("Ray", "ray@qq.com", 18, true, merchantList);
// JsonSerializer 实现
JsonSerializer<List<Merchant>> serializer = new JsonSerializer<List<Merchant>>() {
@Override
public JsonElement serialize(List<Merchant> merchants, Type type, JsonSerializationContext jsonSerializationContext) {
JsonArray jsonMerchant = new JsonArray();
for (Merchant merchant :
merchants) {
jsonMerchant.add("" + merchant.getId());
}
return jsonMerchant;
}
};
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(new TypeToken<List<Merchant>>() {}.getType(), serializer) // 注册
.create();
String userSubscriptionToJson = gson.toJson(userSubscriptionObject);
输出结果
{
"name": "Ray",
"email": "ray@qq.com",
"age": 18,
"isDeveloper": true,
"merchantList": [
"1",
"2"
]
}
我们看到可以看简单的使用 Gson 的自定义序列化,但具体逻辑部分需要我们自己实现。
很多情况是客户端不需要完全匹配服务端返回的 JSON 数据。
应用中很常见 Server 端返回数据并不是我们想要的结构,这种情况就需要我们自定义解析器,使用原始的 JSON 数据,转换成 Java 常见的数据结构或者自定义 model。
假设 JSON:
{
"year": 120,
"month": 2,
"day": 29,
"age": 18,
"email": "ray@qq.com",
"isDeveloper": true,
"name": "Ray"
}
分析以上 JSON,发现有些字段之间没有任何关系,前三个表示年月日,是一个日期结构,后面四个字段表示一个 model 信息,是完全不同的两种类型,所以我们要分开组装成不同的对象。
更好的办法是在解析的内部就搞定它,来看实例:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDate2 {
private String name;
private String email;
private boolean isDeveloper;
private int age;
private Date registerDate;
}
之前已经了解过了序列化的自定义实现,这里使用的方法也是一样的 registerTypeAdapter() 但是实现类变成了 JsonDeserializer,重写的方法也变成了 deserialize() 就不多介绍方法了。
关键是 JsonDeserializer 的实现,同样需要重写 deserialize() 方法,并返回值是我们的 model,所以这里我们要根据自己需求组装我们的 model,具体过程看代码:
JsonDeserializer<UserDate2> deserializer = new JsonDeserializer<UserDate2>() {
@Override
public UserDate2 deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
JsonObject jsonObject = jsonElement.getAsJsonObject();
Date date = new Date(
jsonObject.get("year").getAsInt(),
jsonObject.get("month").getAsInt(),
jsonObject.get("day").getAsInt()
);
return new UserDate2(
jsonObject.get("name").getAsString(),
jsonObject.get("email").getAsString(),
jsonObject.get("isDeveloper").getAsBoolean(),
jsonObject.get("age").getAsInt(),
date
);
}
};
Gson gson = new GsonBuilder()
.registerTypeAdapter(UserDate2.class, deserializer)
.setPrettyPrinting()
.create();
String json = "{\"year\":120,\"month\":2,\"day\":29,\"age\":18,\"email\":\"ray@qq.com\",\"isDeveloper\":true,\"name\":\"Ray\"}";
UserDate2 userDate2 = gson.fromJson(json, UserDate2.class);
可以看到这里不在是一个 new JsonObject(),而是 json.getAsJsonObject(),这里我们通过 JsonObject 的 get 方式,来获取单个字段值,同时需要指定字段值的类型,最后组装并返回我们想要的 UserDate2 类型数据。
实际中需要注意的是:记得使用 has() 方法来检查字段是否存在,以防出现 NullPointerExceptions 异常。
如果 day 字段为空则返回 1号,例如:
jsonObject.has("day") ? jsonObject.get("day").getAsInt() : 1
输出结果
UserDate2(name=Ray, email=ray@qq.com, isDeveloper=true, age=18, registerDate=Sun Mar 29 00:00:00 CST 2020)
之前我们介绍了如何使用 Gson 来自定义(反)序列化和自定义实例创建。使用这些都需要依赖 GsonBuilder 的创建过程,还是有些复杂的,在 Gson 2.7 以后引入一个注解来有效的减少代码即 @JsonAdapter,本文就来介绍下 @JsonAdapter 使用。
注意 @JsonAdapter 是在 Gson 2.7 及以后版本才有
尝试另一种方式,创建一个 JsonSerializer 的实现类 MerchantListSerializer,注意是类。
public class MerchantListSerializer implements JsonSerializer<List<Merchant>> {
@Override
public JsonElement serialize(List<Merchant> merchants, Type type, JsonSerializationContext jsonSerializationContext) {
JsonArray jsonElements = new JsonArray();
for (Merchant merchant :
merchants) {
jsonElements.add("" + merchant.getId());
}
return jsonElements;
}
}
只有是类的形式,我们才能使用注解 @JsonAdapter 来添加 MerchantListSerializer。就像之前的一些注解用法一样,并添加到你需要序列化的 Java model 中。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserSubscriptionAnnotation {
private String name;
private String email;
private int age;
private boolean isDeveloper;
// new
@JsonAdapter(MerchantListSerializer.class)
List<Merchant> merchantList;
}
可以看到 merchantList 被添加了 @JsonAdapter(MerchantListSerializer.class) 注解,而 MerchantListSerializer 正是我们序列化过程的实现类,这样我们就不用使用 GsonBuilder 它来创建的 Gson 对象,而是使用默认创建对象就可以,也不需要那些复杂的设置。
Merchant merchant1 = new Merchant(1, "Future Studio");
Merchant merchant2 = new Merchant(2, "Coffee Shop");
List<Merchant> merchantList = Arrays.asList(merchant1, merchant2);
UserSubscriptionAnnotation subscriptionAnnotation = new UserSubscriptionAnnotation("Ray", "ray@qq.com", 18, true, merchantList);
Gson gson = new Gson();
String fullJson = gson.toJson(subscriptionAnnotation);
这里使用 new Gson() 即可。
输出结果
{"name":"Ray","email":"ray@qq.com","age":18,"isDeveloper":true,"merchantList":["1","2"]}
和序列化类似,我们创建一个 JsonDeserializer 的实现类 UserDateDeserializer,这里也是单独的类。
public class UserDateDeserializer implements JsonDeserializer<UserDate2> {
@Override
public UserDate2 deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
JsonObject jsonObject = jsonElement.getAsJsonObject();
Date date = new Date(
jsonObject.get("year").getAsInt(),
jsonObject.get("month").getAsInt(),
jsonObject.get("day").getAsInt()
);
return new UserDate2(
jsonObject.get("name").getAsString(),
jsonObject.get("email").getAsString(),
jsonObject.get("isDeveloper").getAsBoolean(),
jsonObject.get("age").getAsInt(),
date
);
}
}
和序列化不同的是,@JsonAdapter(UserDateDeserializer.class) 注解是要添加在类级别上面,这是一点不同。
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonAdapter(UserDateDeserializer.class)
public class UserDate2 {
private String name;
private String email;
private boolean isDeveloper;
private int age;
private Date registerDate;
}
String json = "{\"year\":120,\"month\":2,\"day\":29,\"age\":18,\"email\":\"ray@qq.com\",\"isDeveloper\":true,\"name\":\"Ray\"}";
Gson gson = new Gson();
UserDate2 userDate2 = gson.fromJson(json, UserDate2.class);
输出结果
UserDate2(name=Ray, email=ray@qq.com, isDeveloper=true, age=18, registerDate=Sun Mar 29 00:00:00 CST 2020)