前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Gson 系列文章

Gson 系列文章

作者头像
Remember_Ray
发布2020-03-09 13:37:33
14.4K0
发布2020-03-09 13:37:33
举报
文章被收录于专栏:Ray学习笔记Ray学习笔记

今天开始学习 Google 的 Gson 库,Gson 是一个非常强大的库,可以将 JSON 格式的数据转化成 Java 对象,也支持将 Java 对象转成 JSON 数据格式。

JSON 序列化和反序列化入门

Java-JSON 序列化基础

先来看一些序列化例子,Gson 中的序列化意味着将 Java 对象映射成 JSON 数据格式,在接下来的教程中,我们会逐步介绍一些更复杂的情况,首先来看一个简单的例子:

代码语言:javascript
复制
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserSimple {

    private String name;
    private String email;
    private int age;
    private boolean isDeveloper;

}

UserSimple 对象有四个变量:

  • 字符串类型的 name
  • 字符串类型的 email
  • 整型的 age(例如:26)
  • 布尔类型的 isDeveloper(true or false)

Android 或 Java 应用程序需要将 UserSimple 对象转换为 JSON 表示,我们期望是这样的格式:

代码语言:javascript
复制
{
    "name": "Ray",
    "email": "ray@qq.com",
    "age": 18,
    "isDeveloper": true
}

来看下我们是如何用 Gson 转化的,首先要创建一个对象:

代码语言:javascript
复制
UserSimple userObject = new UserSimple("Ray", "ray@qq.com", 18, true);

需要用到 Gson 对象,使用以下构造函数来生成 Gson 对象:

代码语言:javascript
复制
Gson gson = new Gson();

使用 Gson 的 toJson() 方法并传入 UserSimple 对象作为参数:

代码语言:javascript
复制
String userJson = gson.toJson(userObject);

输出JSON格式数据

代码语言:javascript
复制
{"name":"Ray","email":"ray@qq.com","age":18,"isDeveloper":true}

Gson 中字符串被包装成 “”,整型没有被包装,对于单个对象映射使用 Gson 已经足够了。

Java-JSON 反序列化基础

引用上面已经输出的 JSON 格式数据,是最好不过的例子:

代码语言:javascript
复制
String userJson = "{\"name\":\"Ray\",\"email\":\"ray@qq.com\",\"age\":18,\"isDeveloper\":true}";

创建一个 Gson 对象,使用以下构造函数来生成 Gson 对象:

代码语言:javascript
复制
Gson gson = new Gson();

最后,我们通过 fromJson() 方法将JSON映射成一个 Java 对象:

代码语言:javascript
复制
UserSimple userObject = gson.fromJson(userJson, UserSimple.class);

注意第二个参数的传递,否则,Gson 不知道将 JSON 转换成什么类型。

Gson - 映射嵌套对象

我们希望通过实例来演示功能,所以让我们扩展 UserSimple Model 类,下面是扩展之前 UserSimple 类:

代码语言:javascript
复制
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserSimple {

    private String name;
    private String email;
    private int age;
    private boolean isDeveloper;

}

现在我们将 UserAddress 类添加到 UserSimple 中:

代码语言:javascript
复制
@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() 方法,如下:

代码语言:javascript
复制
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);

输出结果

代码语言:javascript
复制
{"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 数据,我们解析成相应的对象。

代码语言:javascript
复制
{
    "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。

代码语言:javascript
复制
@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 来解析了,例如:

代码语言:javascript
复制
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);

输出结果

代码语言:javascript
复制
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))

Gson - Arrays 和 Lists 对象映射

Array 和 List 差异

在我们介绍序列化之前,我们先来看下 Java 中的两种数据结构:Array 和 List。在 Java 中两者实现方式不同,使用哪一种数据类型取决于你的实际需求,但是在序列化这个问题上,Gson 并不关心这两种数据结构的具体实现。

在 JSON 数据格式中,不存在数组等结构,只是 Java 的具体实现使得这两种数据类型有很大不同。但是在上层它们表示出相同的结构。

Array 和 List 序列化

接下来,我们为restaurant 添加一个 menu 属性,包含两个字段,restaurant 中的菜单可以理解成一个 restaurant 列表。如下:

代码语言:javascript
复制
@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 格式如下:

代码语言:javascript
复制
{
    "name": "Future Studio Steak House",
    "menus": [
    ...
    ]
}

与嵌套对象类似,我们没有 menus 的直接值,相反,JSON 中通过 “[]” 来包含一个对象,如上所述,在 JSON 数据中,数组和 List 结构是没有区别的。

menu 中包含一些对象,在我们那的 model 中,menus 只是其中的一个变量,我们先来手动生成一个完整的 JSON 数据。

通过下面这种方式,我们来模拟一个完整的 restaurant 数据:

代码语言:javascript
复制
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);

输出结果

代码语言:javascript
复制
{"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。

代码语言:javascript
复制
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);

输出结果

代码语言:javascript
复制
[{"description":"Spaghetti","price":7.99},{"description":"Steak","price":12.99},{"description":"Salad","price":5.99}]

JSON 中的 “[“ 表示一个对象列表开始,”{“ 表示一个对象开始了,我们应该记住 JSON 数据中格式差别。

Array 和 List 反序列化

来看一个列表最为根节点的例子:

代码语言:javascript
复制
[
    {
        "name": "Christian",
        "flowerCount": 1
    },
     {
        "name": "Marcus",
        "flowerCount": 3
    },
     {
        "name": "Norman",
         "flowerCount": 2
   }
]

根据之前介绍的,”[]”标示一个GSON 解析列表的开始和结束,我们还需要一个具体的 Java model类:

代码语言:javascript
复制
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Founder {

    private String name;
    private int flowerCount;
}

接下来,我们将数据解析成我们想要的数据类型。

Array

首先看解析成数组,通过 Gson 的 gson.fromJson 方法,我们很简单的将 GSON 解析成数组,注意这里传递的参数是 Founder[].class 而不是Founder.class ,如下:

代码语言:javascript
复制
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

List

由于Java泛型的实现机制,使用了泛型的代码在运行期间相关的泛型参数的类型会被擦除,我们无法在运行期间获知泛型参数的具体类型(所有的泛型类型在运行时都是Object类型)。

实际开发中,我们更多的是转成一个 ArrayList,但是,我们不能像解析数组那样传入 List,为了让Gson知道我们要解析的数据类型,我们必须传递给它一个Type,内部根据 TypeToken 来区分要解析的类型。

代码语言:javascript
复制
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

列表作为对象的一部分

我们现在有一个这样的数据:

代码语言:javascript
复制
{
 "name": "Future Studio Dev Team",
 "website": "https://futurestud.io",
 "founders": [{
     "name": "Christian",
     "flowerCount": 1
 }, {
     "name": "Marcus",
     "flowerCount": 3
 }, {
     "name": "Norman",
     "flowerCount": 2
 }]
}

我们需要创建一个用来对应的 Java Model 类:

代码语言:javascript
复制
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GeneralInfo {

    private String name;
    private String website;

    private List<Founder> founders;
}

列表中 Model 类中的一个好处就是,我们在使用 Gson 解析时不再需要传递 TypeToken,直接传入类即可。

代码语言:javascript
复制
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);

执行结果

代码语言:javascript
复制
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,也可以解析为数组格式。

Gson - Map 结构映射

Java Map 序列化

Java 中的 Map 是一个非常灵活的数据结构,被用在很多场景,使用 Java 可以自由的实现应用。

先来看一个例子,使用 HashMap 实现:

代码语言:javascript
复制
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);

输出结果

代码语言:javascript
复制
{"A":["AA","AAA","AAAA"],"B":["BB","BBB","BBBB"],"C":["CC","CCC","CCCC"]}

正如 Java Map 结构一样,每个 Key 对应这个一个集合。

Java Map 反序列化

以下是区分 JSON 数据类型的一些简单的方法:

  • 根据你的文档解释或者应用场景
  • 键和值是否是一种类型
  • 键和值是否是动态的

这里我们需要创建一个map类型的TypeToken类型。

代码语言:javascript
复制
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 对象。

Gson - Set 结构映射

Java Set 序列化

Java 集合中包含很多中数据结构,我们已经了解了 List 和 Map 的使用,这两种结构在 JSON 格式表现上是不一样的。今天我们来看下 Set 数据结构,HastSet 是一个保证值唯一的集合,在 Java 中有很多应用,不用担心,我们使用 Gson 可以轻松搞定 Set 的序列化。

先来看一个例子,使用 HashSet 实现

代码语言:javascript
复制
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 是一种和实现无关的数据结构,能被用来方便的传递。

Java Set 反序列化

刚刚说过 List 和 Set 的 JSON 数据相同,Gson 可以帮我们转成其他数据类型,还是 List 中那个例子:

代码语言:javascript
复制
[
    {
        "name": "Christian",
        "flowerCount": 1
    },
     {
        "name": "Marcus",
        "flowerCount": 3
    },
     {
        "name": "Norman",
         "flowerCount": 2
   }
]

使用方法和之前映射 List 类型一致的 ,这里我们需要创建一个 Set 类型的 TypeToken类型,如下:

代码语言:javascript
复制
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-空值映射

我们来看一个有趣的问题,Gson中是如何处理 null 情况的?

处理空值

我们使用之前的 model 类 UserSimple,不过name字段我们给赋值为null,如下:

代码语言:javascript
复制
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 反序列化得到的结果:

代码语言:javascript
复制
UserSimple(name=null, email=ray@qq.com, age=18, isDeveloper=true)

我们看到只有和 JSON 字段匹配的才被映射成功。

再来看一个字段不全的例子:

代码语言:javascript
复制
{
    "email": "ray@qq.com",
    "name": "Ray"
}

输出结果

代码语言:javascript
复制
UserSimple(name=Ray, email=ray@qq.com, age=0, isDeveloper=false)

我们发现没有被映射成功的字段都被赋值为该类型的默认值,例如:age = 0,boolean 类型的 isDeveloper = false。

Gson Model Annotations

@SerializedName 更改字段命名

@SerializedName 是另一个非常实用的注解。@SerializedName 注解更改了自动匹配 JSON 字段的方式,平时开发中,我们总是默认保持 Java 类属性字段名和 JSON 的字段是一一对应,可有使用并不是总是这样的情况,也许你没有访问继承 Java 类或者你必须遵守公司的命名规则,这就需要使 @SerializedName 注解来匹配 Gson 字段,是一种非常优雅的方式。

代码语言:javascript
复制
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserSimple {

    private String name;
    private String email;
    private int age;
    private boolean isDeveloper;

}

如果没有使用 @Expose, UserSimple 类中的字段将都会被一一映射。

但是,假设 API 发生改变导致返回的 JSON 数据格式发生更改,接口返回了 “fullName” 而不是”name”。

代码语言:javascript
复制
{ 
    "age": 18,
    "email":  "ray@qq.com", 
    "fullName": "Ray", 
    "isDeveloper": true
}

不用担心,我们不要改动任何代码,我们只需要给类中改变的字段添加注解即可。

代码语言:javascript
复制
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserSimple {

    @SerializedName("fullName")
    private String name;
    private String email;
    private int age;
    private boolean isDeveloper;

}

使用 Gson 进行注解,并在此运行,这就是是使用注解自动匹配的好处。

当然,你可以使用注解 @SerializedName 方式来保证命名规范,同时又可以正常映射接口字段,如果你的接口字段和命名规则差别很大,使用@SerializedName 注解来解决还是有必要的。

@SerializedName 匹配多个反序列化名称

我们将介绍如何将多个字段名称映射到一个字段属性上。如果你的应用有多个接口数据,那么使用 @SerializedName 注解就可以解决,你仍然可以使用原来的model类,而不必增加新的model类。

如果你的服务器接口返回了不同的字段名,你可以使用 @SerializedName 注解来解决这个问题。

但是,还不能满足我们的需求,SerializedName 接受两个参数,value、alternate,第一个value是默认参数,如果你只填了一个参数,那这个值将会赋值给 value,第二个 alternate 就为空,同样都填上也是可以的,也可以使用 “ {} “ 传多个值,如果 json 中同时存在多个匹配的值,只会取 alternate 中最后一个匹配的值。

代码语言:javascript
复制
@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上。无论哪一个匹配都会映射。

例如:

代码语言:javascript
复制
{    
    'fullName': 'Ray',   
    'email': 'ray@qq.com'
}

{    
    'userName': 'Ray',   
    'email': 'ray@qq.com'
}

再来看一个例子:

代码语言:javascript
复制
{
    'username': 'Ray1',   
    'fullName': 'Ray2',   
    'email': 'ray@qq.com'
}

如果多个字段匹配一个变量字段,谁在后面 Gson 就会将值赋给谁,结果最终是 name:Ray2。

Gson Builder — 基础和命名规则

GsonBuider 基础

之前我们经常使用这样来生成一个 Gson 对象,Gson gson = new Gson(); 这是标准的 Gson生成方式,不过 Gson 提供了扩展,如果你有一些特殊的需求,你可以使用 GsonBuider 来自定义 Gson。

代码语言:javascript
复制
// 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,我们根据例子来说明。

代码语言:javascript
复制
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserNaming {

    private String Name;
    private String email_of_developer;
    private boolean isDeveloper;
    private int _age;
    
}

可以看到,我们给不同类型的变量命名了不同的规则的字段名,这样做主要是为了之后自定义设置。

代码语言:javascript
复制
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.IDENTITY).create();

先不用理解 FieldNamingPolicy.IDENTITY,这段代码的意思是,我们的 Gson 使用了 FieldNamingPolicy.IDENTITY 的自定义设置,接下来的几个例子我们将介绍 FieldNamingPolicy 中的其他属性。

FieldNamingPolicy.IDENTITY

FieldNamingPolicy.IDENTITY 的作用是,完全匹配我们 Java model 中的字段名,不管你有没有设置其他注解属性,我们使用以下代码来生成一段 JSON 数据:

代码语言:javascript
复制
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.IDENTITY).create();

UserNaming userNamingObject = new UserNaming("Ray", "qq@qq.com", true, 18);

String toJson = gson.toJson(userNamingObject);

输出结果

代码语言:javascript
复制
{"Name":"Ray","email_of_developer":"qq@qq.com","isDeveloper":true,"_age":18}

可以看到字段名就是我们 model 中的字段名,没有发生改变,如果你使用默认生成实例或者不使用 GsonBuilder 自定义生成,最后都会得到相同的结果。

FieldNamingPolicy - LOWER_CASE_WITH_UNDERSCORES

代码语言:javascript
复制
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);

输出结果

代码语言:javascript
复制
{"name":"Ray","email_of_developer":"qq@qq.com","is_developer":true,"_age":18}

使用 FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES 规则,将修改生成的 JSON 中的字段名,格式将全部变成小写,并且每个单词用“_” 分割。

查看源码

代码语言:javascript
复制
LOWER_CASE_WITH_UNDERSCORES {
    public String translateName(Field f) {
        return separateCamelCase(f.getName(), "_").toLowerCase(Locale.ENGLISH);
    }
}

FieldNamingPolicy - LOWER_CASE_WITH_DASHES

有了上面的理解,这个就很好理解了,除了每个单词用“-” 分隔意外其他和 FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES 都一样

输出结果

代码语言:javascript
复制
{"name":"Ray","email_of_developer":"qq@qq.com","is-developer":true,"_age":18}

FieldNamingPolicy - UPPER_CAMEL_CASE

这个就更简单了,规则是每个单词的第一个字母都要大写,其他不变

输出结果

代码语言:javascript
复制
{"Name":"Ray","Email_of_developer":"qq@qq.com","IsDeveloper":true,"_Age":18}

看结果就可以发现,采用了驼峰命名规则,_Age 看起来比较特殊,但它是正确的命名方法。

FieldNamingPolicy - UPPER_CAMEL_CASE_WITH_SPACES

规则如下:

  1. 每个单词的第一个字母会大写
  2. 每个单词使用空格分隔

含有 “_” 的链接的不会在使用空格。

输出结果

代码语言:javascript
复制
{"Name":"Ray","Email_of_developer":"qq@qq.com","Is Developer":true,"_Age":18}

自定义命名规则

@SerializedName 可能不能满足你的需求规则,我们将使用 GsonBuilder的 setFieldNamingStrategy 方法 ,FieldNamingStrategy 自定义规则。例如:

代码语言:javascript
复制
//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);

输出结果

代码语言:javascript
复制
{"Name":"Ray","emailofdeveloper":"qq@qq.com","isDeveloper":true,"age":18}

上面将会把 “_” 剔除,需要注意的是一次只能有一个规则,如果使用多个规则,将以最后一个为准。

反序列化

以上都是关于序列化的操作,统一以上的规则也使用于 JSON 的反序列化操作,如果你的服务器返回的 JSON 字段是以小写和下划线作为规则的话,就可以使用 LOWER_CASE_WITH_UNDERSCORES 规则来匹配字段名称。

例如:

代码语言:javascript
复制
{
    "reviewer_name": "Ray"
}

可以这样定义 Java model 类:

代码语言:javascript
复制
public class PostReviewer {
      private String reviewerName; //不用分隔线,采用驼峰命名
}

Gson Builder — 序列化空值

序列化空值

之前的例子中,空值的映射中,如果你的数据结构中没有给字段赋值或者设置 null,序列化的 JSON 中是不会出现该字段的。

这种通过忽略null值设定的好处就是减少了 JSON 输出的大小。但是我们有时候并不想这样,有一些需求我们必须将所有的字段都要显示出来,不管是赋值没赋值,空还是不为空。

幸运的是 Gson 提供了设置的方法,我们将使用 GsonBuilder 来定制我们的规则。

代码语言:javascript
复制
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() 即可。

Gson Builder — 忽略策略

使用 @Expose 忽略字段

@Expose 默认有两个属性:serialize 和 deserialize,默认值都为 true。

如果你给字段设置了 @Expose 注解,但是没有设置serialize 和 deserialize,那 model 中标记注解的字段都将会输出。

添加 @Expose 注解是一个非常简单的控制哪些要被(反)序列化的方式。我们建议如果需要所有的都被转化,就不用添加 @Expose 注解了,不然只会搞乱你的模型类。

该注解只有在你使用 GsonBuilder 去构造Gson时,同时调用 excludeFieldsWithoutExposeAnnotation()方法才起作用。 如果你使用 new Gson() 实例化一个对象的话,那么 @Expose 的注解是无效的,User中的参数都是会参与反序列化或序列化。

方式一(忽略)

@Expose 注解上配置属性 serialize = false, deserialize = false ,代表这个 emailAddress 不参与序列化或者反序列化,其他正常显示。

代码语言:javascript
复制
@Data
public class User {

    public String name;
    public int age;

    @Expose(serialize = false, deserialize = false)
    public String emailAddress;
}
代码语言:javascript
复制
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 属性,而 nameage 属性会被忽略。

代码语言:javascript
复制
@Data
public class User {

    public String name;
    public int age;

    @Expose()
    public String emailAddress;
}

输出结果

代码语言:javascript
复制
System.out.println(user);  // User(emailAddress=zhangsan@ceshi.com)

注意:方法二在序列化的时候,输出的结果是 {“emailAddress”:”zhangsan@ceshi.com“}

使用 transient

如果使用 transient 来描述字段,将不能被序列化和反序列化

代码语言:javascript
复制
@Data
public class User {

    public String name;
    public int age;

    public transient String emailAddress;
}

使用忽略策略 @Expose 和 transient 失效

之前使用 @Expose 和 transient 来改变单个字段的序列化和反序列化规则。

代码语言:javascript
复制
@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。

代码语言:javascript
复制
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 都将被忽略。

输出结果

代码语言:javascript
复制
{"_name":"Ray","email":"ray@qq.com","age":18}

另一个方法 shouldSkipField 是用来忽略单个字段的,如果你想要忽略带有 “_” 的字段,可以这么写:

代码语言:javascript
复制
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

输出结果

代码语言:javascript
复制
{"email":"ray@qq.com","age":18}

有了 ExclusionStrategies 我们可以根据自己的需求定制更多不同的规则,需要注意创建对应的 GsonBuilder,并正确使用方法。

UserDate 类中只是一些简单的数据类型,还有一些更复杂的情况,同样都可以使用 ExclusionStrategies 规则来帮我们实现,理论上 Gson 能帮我们忽略任何类。

关于 Exclusion Strategies 序列化使用

上面的例子使用的是 setExclusionStrategies 方法,不管是序列化还是反序列化都会起作用,如果我们只想其中一个起作用,选择调下面的方法就行了:

  • addSerializationExclusionStrategy()
  • addDeserializationExclusionStrategy()

用法和 ExclusionStrategy 的实现一样,可重写两个方法实现。

基于 Modifiers 的忽略规则

GsonBuilder 提供 excludeFieldsWithModifiers() 方法,让我们可以自定义可忽略类型,要用到 java.lang.reflect.Modifier 类。

代码语言:javascript
复制
@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 类型的字段,需要这么配置:

代码语言:javascript
复制
//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);

输出结果

代码语言:javascript
复制
{"name":"Ray","age":0}

-------------- 使用前后 --------------

{"name":"Ray","email":"ray@qq.com"}

达到这样的效果,是因为我们重新设置了 excludeFieldsWithModifiers() 方法的参数,Modifier.STATIC, Modifier.FINAL,根据这两个类型,就可以只忽略设置的类型,如果参数为空,将不起作用。

@Expose 使用

这个之前已经介绍了,可查看这篇文,需要注意的是手动设置 excludeFieldsWithoutExposeAnnotation() 方法才能生效。

Gson Builder — Gson Lenient 属性

我们知道 JSON 必须符合一定的标准才能被解析成功,在 JsonReader 的源码中我们看到 Gson 遵循的是 RFC4627 规则,本文将介绍下 Gson 和 JSON 规范的关系。

默认的 Lenient

通常我们将 Java 对象序列化成 JSON 格式的数据时,并不会有什么太大的问题,此时的 JSON 将是一个标准的格式,重点是反序列化可能会有问题。

Gson 内部使用的是 JsonReader 类,看源码能发现里面有一个 lenient 的属性,默认是 false,也就是说默认值接受标准的 JSON 格式数据,如果数据有问题,将抛出异常解析失败。

JsonReader 也提供了设置 lenient 属性的方法,来忽略一些不标准的 JSON 数据格式。

不过我们建议还是要使用标准的 JSON 数据格式,除非你有特殊情况。

如果你开启了 lenient 方式,Gson 的只能帮我们忽略掉以上的格式错误,如果你的 JSON 中有其他错误,Gson 将会抛出 MalformedJsonException 异常,这样你必须要检查下你的 JSON 数据的合法性。

Gson Builder — 特殊类型 Floats & Doubles

这两种类型是 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 标准的。

代码语言:javascript
复制
UserFloat userFloat = new UserFloat("Norman", Float.POSITIVE_INFINITY);

Gson gson = new Gson();

String toJson = gson.toJson(userFloat);

输出结果【异常】

代码语言:javascript
复制
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() 方法

代码语言:javascript
复制
UserFloat userFloat = new UserFloat("Norman", Float.POSITIVE_INFINITY);

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

String toJson = gson.toJson(userFloat);

执行结果

代码语言:javascript
复制
{"name":"Norman","weight":Infinity}

注意:有了这个方法,并不意味着你可以随意的配置 JSON 数据格式,建议还是要遵循标准的 JSON 规范。

Gson Advanced — 映射枚举类型

Enum 序列化

枚举在 Java 中使用非常普遍,可以使用它来规范一些常量的取值。但对于 JSON 来说,同样是不存在枚举类型,如果在解析中使用了枚举,将会发生什么?

先来定义一个枚举 Day:

代码语言:javascript
复制
public enum Day {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
}

在创建一个 Java model 类:

代码语言:javascript
复制
@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 对象:

代码语言:javascript
复制
UserDayEnum userDayEnum = new UserDayEnum("Ray", "ray@qq.com", true, 18, Day.SUNDAY);

Gson gson = new Gson();

String userJson = gson.toJson(userDayEnum);

输出结果

代码语言:javascript
复制
{"_name":"Ray","email":"ray@qq.com","isDeveloper":true,"age":18,"day":"SUNDAY"}

根据结果我们看到,不用做任何配置处理,Gson 就帮我们正常输出了 JSON 格式的数据。

Enum 反序列化

反序列化也非常简单,同样不做任何额外的配置:

代码语言:javascript
复制
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);

输出结果

代码语言:javascript
复制
UserDayEnum(_name=Ray, email=ray@qq.com, isDeveloper=true, age=18, day=SUNDAY)

自定义枚举(反)序列化

使用 @SerializedName 注解给每个变量赋予另一个字段名:

代码语言:javascript
复制
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 数据来(反)序列化:

代码语言:javascript
复制
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);

输出结果

代码语言:javascript
复制
{"_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 Advanced — 泛型

泛型序列化

之前使用 Gson 来解析 Java 对象,我们必须传入要解析的 Java class 类型,先来看例子。

代码语言:javascript
复制
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());

输出结果

代码语言:javascript
复制
[1,2,3]  // integerJson

["1","2","3"]  // stringJson

再来看一个泛型封装的例子 Box,只包含了一个泛型对象:

代码语言:javascript
复制
@Data
public class Box<T> {

    private T boxContent;

    public Box(T boxContent) {
        this.boxContent = boxContent;
    }
}
代码语言:javascript
复制
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);

输出结果

代码语言:javascript
复制
{"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 泛型来解析。

代码语言:javascript
复制
{
  "boxContent": {
    "_name": "Norman",
    "age": 26,
    "email": "norman@fs.io",
    "isDeveloper": true,
    "registerDate": "Jun 7, 2016 7:15:29 AM"
  }
}

首先我们要明确 JSON 属于 Box 中泛型的哪种类型,知道了泛型的类型,我们就能确认 TypeToken 的类型。

代码语言:javascript
复制
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);

输出结果

代码语言:javascript
复制
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 Advanced — 简单自定义序列化

本文将实现一个自定义的 Gson serialization 过程,有些情况我们可能会考虑到自定义,例如:和 server 通信时,有时候不需要传递一个完成 JSON 信息,只需要关键信息即可,接下来我们一步一步分析。

自定义序列化

有个这样的场景:App 从 服务器获取一个列表,用户可以订阅列表中的每一项,但是要将订阅的某一条发送给服务器同步。

代码语言:javascript
复制
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Merchant {

    private int id;
    private String name;
    
}
代码语言:javascript
复制
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserSubscription {

    private String name;
    private String email;
    private int age;
    private boolean isDeveloper;

    List<Merchant> merchantList;
    
}

序列化

代码语言:javascript
复制
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);

输出结果

代码语言:javascript
复制
{
  "name": "Ray",
  "email": "ray@qq.com",
  "age": 18,
  "isDeveloper": true,
  "merchantList": [
    {
      "id": 1,
      "name": "Future Studio"
    },
    {
      "id": 2,
      "name": "Coffee Shop"
    }
  ]
}

这是一个非常标准 JSON 的输出,但是在实际中,不可能是这么小的数据量,而且 Merchant 结构可能非常复杂,这就会造成 JSON 数据非常大,从而导致解析或传递耗时

使用 @Expose 简化

我们第一个想到的方法是减少 Merchant 中不必要字段的序列化,使用之前学过的 @Expose 来简化 JSON,我们来调整下 Merchant:

代码语言:javascript
复制
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Merchant {

    @Expose
    private int id;

    @Expose(serialize = false)
    private String name;

}

UserSubscription 也需要调整

代码语言:javascript
复制
@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 配置

代码语言:javascript
复制
Gson gson = new GsonBuilder()
        .setPrettyPrinting()
        .excludeFieldsWithoutExposeAnnotation() // @Expose 生效
        .create();

输出结果

代码语言:javascript
复制
{
  "name": "Ray",
  "email": "ray@qq.com",
  "age": 18,
  "isDeveloper": true,
  "merchantList": [
    {
      "id": 1
    },
    {
      "id": 2
    }
  ]
}

从结果上看已经减少输出了 name 字段,看起来已经优化了,在属性较多的时候改动是非常大的(不推荐)。但是问题又来了,如果其他接口,需要一个完整的 JSON 结果,这该怎么办?

简化自定义序列化为单个对象

通过 @Expose 是能解决一部分问题,但是存在局限性,现在我们使用自定义来解决这些问题,作法不干涉 Merchant 类,只在干涉序列化过程。

为了实现自定义需求,我们需要使用 GsonBuilder 来帮我们生成 Gson 实例,需要给 Merchant 类注册一个 adapter:

代码语言:javascript
复制
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 字段。

根据自己的需求选择调用响应的方法,再次输出:

代码语言:javascript
复制
{
  "name": "Ray",
  "email": "ray@qq.com",
  "age": 18,
  "isDeveloper": true,
  "merchantList": [
    {
      "id": 1
    },
    {
      "id": 2
    }
  ]
}

可以看到上面的结果和使用 @Expose 的结果是一样的。

简化自定义序列化 List 对象

和自定义序列化对象类似,我们只需要更改下 JsonSerializer 中的类型为 List 即可:

代码语言:javascript
复制
  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);

输出结果

代码语言:javascript
复制
{
  "name": "Ray",
  "email": "ray@qq.com",
  "age": 18,
  "isDeveloper": true,
  "merchantList": {
    "ids": "1,2"
  }
}

我们可以看到,merchantList 中,大大减少了输出,这对于客户端和服务器都是有利的,有利于提升访问速度和性能。但是 merchantList 中的数据格式有些问题。

简化自定义序列化 List 为数组

针对上一节的问题,我们只需要修改 serialize方法中的实现即可,不在使用 JsonObject,而是使用 JsonArray,实现如下:

代码语言:javascript
复制
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);

输出结果

代码语言:javascript
复制
{
  "name": "Ray",
  "email": "ray@qq.com",
  "age": 18,
  "isDeveloper": true,
  "merchantList": [
    "1",
    "2"
  ]
}

我们看到可以看简单的使用 Gson 的自定义序列化,但具体逻辑部分需要我们自己实现。

Gson Advanced — 自定义反序列化基础

很多情况是客户端不需要完全匹配服务端返回的 JSON 数据。

自定义反序列化

应用中很常见 Server 端返回数据并不是我们想要的结构,这种情况就需要我们自定义解析器,使用原始的 JSON 数据,转换成 Java 常见的数据结构或者自定义 model。

假设 JSON:

代码语言:javascript
复制
{
	"year": 120,
	"month": 2,
	"day": 29,
	"age": 18,
	"email": "ray@qq.com",
	"isDeveloper": true,
	"name": "Ray"
}

分析以上 JSON,发现有些字段之间没有任何关系,前三个表示年月日,是一个日期结构,后面四个字段表示一个 model 信息,是完全不同的两种类型,所以我们要分开组装成不同的对象。

更好的办法是在解析的内部就搞定它,来看实例:

代码语言:javascript
复制
@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,具体过程看代码:

代码语言:javascript
复制
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号,例如:

代码语言:javascript
复制
jsonObject.has("day") ? jsonObject.get("day").getAsInt() : 1

输出结果

代码语言:javascript
复制
UserDate2(name=Ray, email=ray@qq.com, isDeveloper=true, age=18, registerDate=Sun Mar 29 00:00:00 CST 2020)

Gson Advanced — 通过 @JsonAdapter 自定义(反)序列化

之前我们介绍了如何使用 Gson 来自定义(反)序列化和自定义实例创建。使用这些都需要依赖 GsonBuilder 的创建过程,还是有些复杂的,在 Gson 2.7 以后引入一个注解来有效的减少代码即 @JsonAdapter,本文就来介绍下 @JsonAdapter 使用。

@JsonAdapter 注解

注意 @JsonAdapter 是在 Gson 2.7 及以后版本才有

自定义序列化

尝试另一种方式,创建一个 JsonSerializer 的实现类 MerchantListSerializer,注意是类。

代码语言:javascript
复制
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 中。

代码语言:javascript
复制
@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 对象,而是使用默认创建对象就可以,也不需要那些复杂的设置。

代码语言:javascript
复制
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() 即可。

输出结果

代码语言:javascript
复制
{"name":"Ray","email":"ray@qq.com","age":18,"isDeveloper":true,"merchantList":["1","2"]}

自定义反序列化

和序列化类似,我们创建一个 JsonDeserializer 的实现类 UserDateDeserializer,这里也是单独的类。

代码语言:javascript
复制
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) 注解是要添加在类级别上面,这是一点不同。

代码语言:javascript
复制
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonAdapter(UserDateDeserializer.class)
public class UserDate2 {

    private String name;
    private String email;
    private boolean isDeveloper;
    private int age;
    private Date registerDate;

}
代码语言:javascript
复制
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);

输出结果

代码语言:javascript
复制
UserDate2(name=Ray, email=ray@qq.com, isDeveloper=true, age=18, registerDate=Sun Mar 29 00:00:00 CST 2020)
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-02-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • JSON 序列化和反序列化入门
    • Java-JSON 序列化基础
      • Java-JSON 反序列化基础
      • Gson - 映射嵌套对象
        • 序列化嵌套对象
          • 反序列化嵌套对象
          • Gson - Arrays 和 Lists 对象映射
            • Array 和 List 差异
              • Array 和 List 序列化
                • Array 和 List 反序列化
                  • Array
                  • List
                  • 列表作为对象的一部分
              • Gson - Map 结构映射
                • Java Map 序列化
                  • Java Map 反序列化
                  • Gson - Set 结构映射
                    • Java Set 序列化
                      • Java Set 反序列化
                      • Gson-空值映射
                        • 处理空值
                        • Gson Model Annotations
                          • @SerializedName 更改字段命名
                            • @SerializedName 匹配多个反序列化名称
                            • Gson Builder — 基础和命名规则
                              • GsonBuider 基础
                                • 命名规则
                                  • FieldNamingPolicy.IDENTITY
                                  • FieldNamingPolicy - LOWER_CASE_WITH_UNDERSCORES
                                  • FieldNamingPolicy - LOWER_CASE_WITH_DASHES
                                  • FieldNamingPolicy - UPPER_CAMEL_CASE
                                  • FieldNamingPolicy - UPPER_CAMEL_CASE_WITH_SPACES
                                • 自定义命名规则
                                • Gson Builder — 序列化空值
                                  • 序列化空值
                                  • Gson Builder — 忽略策略
                                    • 使用 @Expose 忽略字段
                                      • 方式一(忽略)
                                      • 方式二(不忽略)
                                    • 使用 transient
                                      • 使用忽略策略 @Expose 和 transient 失效
                                        • 关于 Exclusion Strategies 序列化使用
                                          • 基于 Modifiers 的忽略规则
                                            • @Expose 使用
                                            • Gson Builder — Gson Lenient 属性
                                              • 默认的 Lenient
                                              • Gson Builder — 特殊类型 Floats & Doubles
                                              • Gson Advanced — 映射枚举类型
                                                • Enum 序列化
                                                  • Enum 反序列化
                                                    • 自定义枚举(反)序列化
                                                    • Gson Advanced — 泛型
                                                      • 泛型序列化
                                                        • 反序列化泛型
                                                        • Gson Advanced — 简单自定义序列化
                                                          • 自定义序列化
                                                            • 使用 @Expose 简化
                                                              • 简化自定义序列化为单个对象
                                                                • 简化自定义序列化 List 对象
                                                                  • 简化自定义序列化 List 为数组
                                                                  • Gson Advanced — 自定义反序列化基础
                                                                    • 自定义反序列化
                                                                    • Gson Advanced — 通过 @JsonAdapter 自定义(反)序列化
                                                                      • @JsonAdapter 注解
                                                                        • 自定义序列化
                                                                          • 自定义反序列化
                                                                          领券
                                                                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档