本次的主要内容

  • 字段过滤的几种方法
    • 基于@Expose 注解
    • 基于版本
    • 基于访问修饰符
    • 基于策略(作者最常用)
  • POJO 与 JSON 的字段映射规则

一、字段过滤的几种方法

字段过滤 Gson 中比较常用的技巧,特别是在 Android 中,在处理业务逻辑时可能需要在设置的 POJO 中加入一些字段,但显然在序列化的过程中是不需要的,并且如果序列化还可能带来一个问题就是循环引用 ,那么在用 Gson 序列化之前为不防止这样的事件情发生,你不得不作另外的处理。

以一个商品分类 Category 为例:

  1. {
  2. "id": 1,
  3. "name": "电脑",
  4. "children": [
  5. {
  6. "id": 100,
  7. "name": "笔记本"
  8. },
  9. {
  10. "id": 101,
  11. "name": "台式机"
  12. }
  13. ]
  14. }

一个大分类,可以有很多小分类,那么显然我们在设计 Category 类时 Category 本身既可以是大分类,也可以是小分类。

  1. public class Category {
  2. public int id;
  3. public String name;
  4. public List<Category> children;
  5. }

但是为了处理业务,我们还需要在子分类中保存父分类,最终会变成下面的情况:

  1. public class Category {
  2. public int id;
  3. public String name;
  4. public List<Category> children;
  5. // 因业务需要增加,但并不需要序列化
  6. public Category parent;
  7. }

但是上面的 parent 字段是因业务需要增加的,那么在序列化是并不需要,所以在序列化时就必须将其排除,那么在 Gson 中如何排除符合条件的字段呢?下面提供4种方法,大家可根据需要自行选择合适的方式。

1.1 基于 @Expose 注解

@Expose 提供了两个属性,且都有默认值,开发者可以根据需要设置不同的值。
Gson 使用指南(三) - 图1
@Expose
@Expose 注解从名字上就可以看出是暴露的意思,所以该注解是用于对外暴露字段的。可是我们以前用 Gson 的时候也没有 @Expose 注解还不是正确的序列化为 JSON 了么?是的,所以该注解在使用 new Gson() 时是不会发生作用。毕竟最常用的 API 要最简单,所以该注解必须和 GsonBuilder 配合使用。

使用方法:简单说来就是需要导出的字段上加上 @Expose 注解,不导出的字段不加。注意,是不导出的不加

  1. @Expose
  2. @Expose(deserialize = true, serialize = true) // 序列化和反序列化都都生效,等价于上一条
  3. @Expose(deserialize = true, serialize = false) // 反序列化时生效
  4. @Expose(deserialize = false, serialize = true) // 序列化时生效
  5. @Expose(deserialize = false, serialize = false) // 和不写注解一样

注:根据上面的图片可以得出,所有值为 true 的属性都是可以不写的(默认值是 true)。

拿上面的例子来说就是:

  1. public class Category {
  2. @Expose public int id;
  3. @Expose public String name;
  4. @Expose public List<Category> children;
  5. // 不需要序列化,所以不加 @Expose 注解
  6. // 等价于 @Expose(deserialize = false,serialize = false)
  7. public Category parent;
  8. }

在使用 Gson 时也不能只是简单的 new Gson() 了。

  1. Gson gson = new GsonBuilder()
  2. .excludeFieldsWithoutExposeAnnotation()
  3. .create();
  4. gson.toJson(category);

1.2 基于版本

Gson 在对基于版本的字段导出提供了两个注解 @Since@Until,和 GsonBuilder.setVersion(Double) 配合使用。@Since@Until 都接收一个 Double 值。
Gson 使用指南(三) - 图2
Since 和 Until 注解
使用方法:当前版本(GsonBuilder 中设置的版本)大于等于 Since 的值时该字段导出,小于 Until 的值时该字段导出。

  1. class SinceUntilSample {
  2. @Since(4)
  3. public String since;
  4. @Until(5)
  5. public String until;
  6. }
  7. public void testSineUtil(double version){
  8. SinceUntilSample sinceUntilSample = new SinceUntilSample();
  9. sinceUntilSample.since = "since";
  10. sinceUntilSample.until = "until";
  11. Gson gson = new GsonBuilder().setVersion(version).create();
  12. System.out.println(gson.toJson(sinceUntilSample));
  13. }
  14. // 当 version < 4 时,结果:{"until":"until"}
  15. // 当 version >= 4 && version < 5 时,结果:{"since":"since","until":"until"}
  16. // 当 version >= 5 时,结果:{"since":"since"}

注:当一个字段被同时注解时,需两者同时满足条件。

1.3 基于访问修饰符

什么是修饰符?publicstaticfinalprivateprotected 这些就是,所以这种方式也是比较特殊的。

使用方式:

  1. class ModifierSample {
  2. final String finalField = "final";
  3. static String staticField = "static";
  4. public String publicField = "public";
  5. protected String protectedField = "protected";
  6. String defaultField = "default";
  7. private String privateField = "private";
  8. }

使用 GsonBuilder.excludeFieldsWithModifiers 构建 Gson,支持 int 型的可变参数,值由 java.lang.reflect.Modifier 提供,下面的程序排除了 privateFieldfinalFieldstaticField 三个字段。

  1. ModifierSample modifierSample = new ModifierSample();
  2. Gson gson = new GsonBuilder()
  3. .excludeFieldsWithModifiers(Modifier.FINAL, Modifier.STATIC, Modifier.PRIVATE)
  4. .create();
  5. System.out.println(gson.toJson(modifierSample));
  6. // ==> {"publicField":"public","protectedField":"protected","defaultField":"default"}

到此为止,Gson 提供的所有注解就还有一个 @JsonAdapter 没有介绍了,而 @JsonAdapter 将和 TypeAdapter 将作为该系列第4篇也是最后一篇文章的主要内容。

1.4 基于策略(自定义规则)

上面介绍的了3种排除字段的方法,说实话我除了 @Expose 以外,其它的都是只在 Demo 用上过,用得最多的就是马上要介绍的自定义规则,好处是功能强大、灵活,缺点是相比其它3种方法稍麻烦一点,但也仅仅只是想对其它3种稍麻烦一点而已。

基于策略是利用 Gson 提供的 ExclusionStrategy 接口,同样需要使用 GsonBuilder,相关 API 2个,分别是 addSerializationExclusionStrategyaddDeserializationExclusionStrategy 分别针对序列化和反序化时。这里以序列化为例。

例如:

  1. Gson gson = new GsonBuilder()
  2. .addSerializationExclusionStrategy(new ExclusionStrategy() {
  3. @Override
  4. public boolean shouldSkipField(FieldAttributes f) {
  5. // 这里作判断,决定要不要排除该字段,return true 为排除
  6. // 按字段名排除
  7. if ("finalField".equals(f.getName())) return true;
  8. // 按注解排除
  9. Expose expose = f.getAnnotation(Expose.class);
  10. if (expose != null && expose.deserialize() == false) return true;
  11. return false;
  12. }
  13. @Override
  14. public boolean shouldSkipClass(Class<?> clazz) {
  15. // 直接排除某个类,return true 为排除
  16. return (clazz == int.class || clazz == Integer.class);
  17. }
  18. })
  19. .create();

有没有很强大?

二、 POJO 与 JSON 的字段映射规则

之前在你真的会用Gson吗?Gson使用指南(二)属性重命名时 介绍了 @SerializedName 这个注解的使用,本节的内容与上一次差不多的,但既然叫映射规则那么说的自然是有规律的情况。

还是之前 User 的例子,已经去除所有注解:

  1. User user = new User("怪盗kidou", 24);
  2. user.emailAddress = "ikidou@example.com";

GsonBuilder 提供了 FieldNamingStrategy 接口和 setFieldNamingPolicysetFieldNamingStrategy 两个方法。

2.1 默认实现

GsonBuilder.setFieldNamingPolicy 方法与 Gson 提供的另一个枚举类 FieldNamingPolicy 配合使用,该枚举类提供了5种实现方式分别为:

FieldNamingPolicy 结果(仅输出 emailAddress 字段)
IDENTITY {"emailAddress":"ikidou@example.com"}
LOWER_CASE_WITH_DASHES {"email-address":"ikidou@example.com"}
LOWER_CASE_WITH_UNDERSCORES {"email_address":"ikidou@example.com"}
UPPER_CAMEL_CASE {"EmailAddress":"ikidou@example.com"}
UPPER_CAMEL_CASE_WITH_SPACES {"Email Address":"ikidou@example.com"}

2.2 自定义实现

GsonBuilder.setFieldNamingStrategy 方法需要与 Gson 提供的 FieldNamingStrategy 接口配合使用,用于实现将 POJO 的字段与 JSON 的字段相对应。上面的 FieldNamingPolicy 实际上也实现了 FieldNamingStrategy 接口,也就是说 FieldNamingPolicy 也可以使用 setFieldNamingStrategy 方法。

使用方式:

  1. Gson gson = new GsonBuilder()
  2. .setFieldNamingStrategy(new FieldNamingStrategy() {
  3. @Override
  4. public String translateName(Field f) {
  5. // 实现自己的规则
  6. return null;
  7. }
  8. })
  9. .create();

注意@SerializedName 注解拥有最高优先级,在加有 @SerializedName 注解的字段上 FieldNamingStrategy 不生效!