本篇文章的主要内容:

  • Gson 的基本用法
  • 属性重命名 @SerializedName 注解的使用
  • Gson 中使用泛型

一、Gson 的基本用法

Gson 提供了 fromJson()toJson() 两个直接用于解析和生成的方法,前者实现反序列化,后者实现了序列化。同时每个方法都提供了重载方法,我常用的总共有5个。

基本数据类型的解析
**

  1. Gson gson = new Gson();
  2. int i = gson.fromJson("100", int.class); // 100
  3. double d = gson.fromJson("\"99.99\"", double.class); // 99.99
  4. boolean b = gson.fromJson("true", boolean.class); // true
  5. String str = gson.fromJson("String", String.class); // String

注:不知道你是否注意到了第2、3行有什么不一样没


基本数据类型的生成
**

  1. Gson gson = new Gson();
  2. String jsonNumber = gson.toJson(100); // 100
  3. String jsonBoolean = gson.toJson(false); // false
  4. String jsonString = gson.toJson("String"); // "String"

POJO 类的生成与解析
**

  1. public class User {
  2. public String name;
  3. public int age;
  4. public String emailAddress;
  5. // 省略 Setter / Getter
  6. }

生成 JSON:

  1. Gson gson = new Gson();
  2. User user = new User("怪盗kidou", 24);
  3. String jsonObject = gson.toJson(user);
  4. // ==> {"name":"怪盗kidou","age":24}

解析 JSON:

  1. Gson gson = new Gson();
  2. String jsonString = "{\"name\":\"怪盗kidou\",\"age\":24}";
  3. User user = gson.fromJson(jsonString, User.class);

二、属性重命名 @SerializedName 注解的使用

从上面 POJO 的生成与解析可以看出 JSON 的字段和值是的名称和类型是一一对应的,但也有一定容错机制(如第一个例子第3行将字符串的99.99转成 double 型,你可别告诉我都是字符串啊),但有时候也会出现一些不和谐的情况,如:

期望的 JSON 格式:

  1. {"name":"怪盗kidou","age":24,"emailAddress":"ikidou@example.com"}

实际:

  1. {"name":"怪盗kidou","age":24,"email_address":"ikidou@example.com"}

这对于使用 PHP 作为后台开发语言时很常见的情况,PHP 和 JS 在命名时一般采用下划线风格,而 Java 中一般采用的驼峰法,让后台的哥们改吧,前端和后台都不爽,但要自己使用下划线风格时我会感到不适应,怎么办?难到没有两全齐美的方法么?

我们知道 Gson 在序列化和反序列化时需要使用反射,说到反射就不得不想到注解,一般各类库都将注解放到 annotations 包下,打开源码在 com.google.gson 包下果然有一个 annotations,里面有一个 SerializedName 的注解类,这应该就是我们要找的。

那么对于 JSON 中 email_address 这个属性对应 POJO 的属性则变成:

  1. @SerializedName("email_address")
  2. public String emailAddress;

这样的话,很好的保留了前端、后台、Android/Java 各自的命名习惯。

你以为这样就完了么?

如果接中设计不严谨或者其它地方可以重用该类,其它字段都一样,就 emailAddress 字段不一样,比如有下面三种情况那怎么?重新写一个?

  1. {"name":"怪盗kidou","age":24,"emailAddress":"ikidou@example.com"}
  1. {"name":"怪盗kidou","age":24,"email_address":"ikidou@example.com"}
  1. {"name":"怪盗kidou","age":24,"email":"ikidou@example.com"}

为 POJO 字段提供备选属性名

SerializedName 注解提供了两个属性,上面用到了其中一个,别外还有一个属性 alternate,接收一个 String 数组。

注:alternate 需要 2.4 版本


  1. @SerializedName(value = "emailAddress", alternate = {"email", "email_address"})
  2. public String emailAddress;

当上面的三个属性(email_addressemailemailAddress)都中出现任意一个时均可以得到正确的结果。

注:当多种情况同时出时,以最后一个出现的值为准。


  1. Gson gson = new Gson();
  2. String json = "{\"name\":\"怪盗kidou\",\"age\":24,\"emailAddress\":\"ikidou_1@example.com\",\"email\":\"ikidou_2@example.com\",\"email_address\":\"ikidou_3@example.com\"}";
  3. User user = gson.fromJson(json, User.class);
  4. System.out.println(user.emailAddress);
  5. // ==> ikidou_3@example.com

三、Gson 中使用泛型

上面了解的 JSON 中的 NumberBooleanObjectString,现在说一下 Array

例:JSON 字符串数组

  1. ["Android","Java","PHP"]

当我们要通过 Gson 解析这个 JSON 时,一般有两种方式:使用数组,使用 List。而 List 对于增删都是比较方便的,所以实际使用是还是 List 比较多。

数组比较简单:

  1. Gson gson = new Gson();
  2. String jsonArray = "[\"Android\",\"Java\",\"PHP\"]";
  3. String[] strings = gson.fromJson(jsonArray, String[].class);

但对于 List 将上面的代码中的 String[].class 直接改为 List<String>.class 是行不通的。对于 Java 来说 List<String>List<User> 这俩个的字节码文件只一个那就是 List.class,这是 Java 泛型使用时要注意的问题 泛型擦除

为了解决的上面的问题,Gson 为我们提供了 TypeToken 来实现对泛型的支持,所以当我们希望使用将以上的数据解析为 List<String> 时需要这样写。

  1. Gson gson = new Gson();
  2. String jsonArray = "[\"Android\",\"Java\",\"PHP\"]";
  3. String[] strings = gson.fromJson(jsonArray, String[].class);
  4. List<String> stringList = gson.fromJson(jsonArray, new TypeToken<List<String>>() {}.getType());

注:TypeToken 的构造方法是 protected 修饰的,所以上面才会写成 new TypeToken<List<String>>() {}.getType() 而不是 new TypeToken<List<String>>().getType()

泛型解析对接口 POJO 的设计影响
**
泛型的引入可以减少无关的代码,如我现在所在公司接口返回的数据分为两类:

  1. {"code":"0","message":"success","data":{}}
  1. {"code":"0","message":"success","data":[]}

我们真正需要的 data 所包含的数据,而 code 只使用一次,message 则几乎不用。如果 Gson 不支持泛型或不知道 Gson 支持泛型的同学一定会这么定义 POJO。

  1. public class UserResponse {
  2. public int code;
  3. public String message;
  4. public User data;
  5. // 省略 Setter / Getter
  6. }

当其它接口的时候又重新定义一个 XXResponsedata 的类型改成 XX,很明显 code,和 message 被重复定义了多次,通过泛型的话我们可以将 codemessage 字段抽取到一个 Result 的类中,这样我们只需要编写 data 字段所对应的 POJO 即可,更专注于我们的业务逻辑。如:

  1. public class Result<T> {
  2. public int code;
  3. public String message;
  4. public T data;
  5. // 省略 Setter / Getter
  6. }

那么对于 data 字段是 User 时则可以写为 Result<User> ,当是个列表的时候为 Result<List<User>>,其它同理。

PS:嫌每次 new TypeToken<Result<XXX>new TypeToken<Result<List<XXX>> 太麻烦, 想进一步封装? 查看另一篇博客:搞定 Gson 泛型封装

结语

本文主要通过代码向各位读者讲解了 Gson 的基本用法,以后还会更新更多更高级的用法,如果你还不熟悉 注解 泛型 那么你要多多努力啦。


4月6日补充

有说看不懂 Result 那段怎么个简化法,下面给个两个完整的例子,UserList<User>

没有引入泛型之前时写法:

  1. public class UserResult {
  2. public int code;
  3. public String message;
  4. public User data;
  5. // 省略 Setter / Getter
  6. }
  7. public class UserListResult {
  8. public int code;
  9. public String message;
  10. public List<User> data;
  11. // 省略 Setter / Getter
  12. }
  13. String json = "{..........}";
  14. Gson gson = new Gson();
  15. UserResult userResult = gson.fromJson(json, UserResult.class);
  16. User user = userResult.data;
  17. UserListResult userListResult = gson.fromJson(json, UserListResult.class);
  18. List<User> users = userListResult.data;

上面有两个类 UserResultUserListResult,有两个字段重复,一两个接口就算了,如果有上百个怎么办?不得累死?所以引入泛型。

  1. // 不再重复定义 Result 类
  2. Type userType = new TypeToken<Result<User>>() {}.getType();
  3. Result<User> userResult = gson.fromJson(json, userType);
  4. User user = userResult.data;
  5. Type userListType = new TypeToken<Result<List<User>>>() {}.getType();
  6. Result<List<User>> userListResult = gson.fromJson(json, userListType);
  7. List<User> users = userListResult.data;

看出区别了么?引入了泛型之后虽然要多写一句话用于获取泛型信息,但是返回值类型很直观,也少定义了很多无关类。