likes
comments
collection
share

Gson 字段排除策略总结

作者站长头像
站长
· 阅读数 2

一般情况 Json 和 Java 实体类 字段都有一一对应的,那么用 Gson 去序列化和反序列化是很简单的事(Gson:你这不废话吗 😤 ,干这个我是专业的。)如果遇到字段我们不想序列化或者反序列化怎么办。 放心,这个问题 Gson 也考虑到了,给我们提供了些方法,让我们去配置。下面就说说 Gson 它的排除方法。

本文测试实体类

public class User {
    private Long id;
    private String name;
    private int age;
    private int sex;
    private String area;

    ……省略构造方法、set/get方法
     @Override
    public String toString() {
      return "User{" +
          "id=" + id +
          ", name='" + name + '\'' +
          ", age=" + age +
          ", sex=" + sex +
          ", area='" + area + '\'' +
          '}';
    }
}

关键字排除

Gson 默认排除被 transientstatic 修饰的字段 被 static 字段 属于类变量,不属于成员变量,把它默认排除很正常。transient关键词是 java 中标记成员变量不被序列化/反序列化,Gson 默认也遵循了这个关键词的规则。所以有时想让 Gson 排除某个成员变量不被序列化和反序列化就可以使用 java 的关键词 transient 来标记 例如 我们把 User area 字段前面加上 transient

public class User {
    private Long id;
    private String name;
    private int age;
    private int sex;
    private transient String area;
}

构建 Gson 对象

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

测试代码

    //<editor-fold desc="json 序列化 serialize">
    User user = new User(1L, "Jack", 20, 1, "shanghai");
    String jsonStr = gson.toJson(user);
    System.out.println("序列化:\n"+jsonStr);
    //</editor-fold>

    //<editor-fold desc="json 反序列化 deserialize">
    String userJsonStr = "{\"id\":2,\"name\":\"Tom\",\"age\":22,\"sex\":1,\"area\":\"上海\"}";
    User user1 = gson.fromJson(userJsonStr, User.class);
    System.out.println("反序列化:\n"+user1.toString());
    //</editor-fold>

下面的例子中如何没特殊声明,都是用的上面同样的测试代码

测试输入结果

序列化:
{"id":1,"name":"Jack","age":20,"sex":1}
反序列化:
User{id=2, name='Tom', age=22, sex=1, area='null'}

transient 标记的 area 字段 在序号化 和饭序号化都被排除了。

除了Gson 默认的关键词排除测量,我们也可以自己定义关键词排除策略,使用下面的方法

/*
 * 使用此方法会覆盖 Gson 默认的设置
 *
*/
public GsonBuilder excludeFieldsWithModifiers(int... modifiers)

比如我们想排除私有字段不被序列化/反序列化,就可以这样写

  Gson gson = new GsonBuilder()
                .excludeFieldsWithModifiers(Modifier.PRIVATE,Modifier.STATIC,Modifier.TRANSIENT)
                .create();

加上 Modifier.STATIC 和 Modifier.TRANSIENT 是因为不想破坏 Gson 的默认设置。

我们把 User 的 id 改成私有,其他变成公共字段

public class User {
    private Long id;
    public String name;
    public int age;
    public int sex;
    public transient String area;
}

测试输入结果

序列化:
{"name":"Jack","age":20,"sex":1}
反序列化:
User{id=null, name='Tom', age=22, sex=1, area='null'}

私有字段 id 和 被 transient 修饰的 area 都没参与序列化和反序列化。

Expose 注解排除

上面关键词排除的方法,会把字段序列化和反序列化中都会排除,那么有没有只排除一个方向的,比如只排除字段的序列化,不排除字段的反序列化。当然有,那就是使用 Expose 注解。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Expose {
  
  /**
   * 值为true 就参与json 的序列化,否则不参与
   * 默认为true
   */
  public boolean serialize() default true;

/**
   * 值为true 就参与json 的反列化,否则不参与
    * 默认为true
   */
  public boolean deserialize() default true;
}

Expose 注解有两个方法,分别控制是否序列化和反序列化,默认都是true。

下面就来测试一下 Expose 的使用效果

public class User {
 	//默认值,参与序列号和反序列化
    @Expose
    private Long id;
    //不参与反序列化
    @Expose(serialize = true,deserialize = false)
    private String name;
    //不参与序列化
    @Expose(serialize = false,deserialize = true)
    private int age;
    private int sex;
    private  String area;
}

要想 Expose 注解起作用,需要构建Gson 的时候调用 excludeFieldsWithoutExposeAnnotation() 这个方法使用

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

现测试结果如下

序列化:
{"id":1,"name":"Jack"}
反序列化: 
User{id=2, name='null', age=22, sex=0, area='null'}

你会发现被 Expose 标识的字段,可以灵活的控制某个字段是否参与序列化和反序列化。但是从输入的结果中可以看出没有被 Expose 的字段序列化和反序列化都没有参与。 也就是说只有 Expose 标识的字段才有可能序列化和反序列化,其它的都被忽略了。从 excludeFieldsWithoutExposeAnnotation() 这个函数名也看出它的意思就是排除不带 Expose 注解的字段。所以划重点:使用 excludeFieldsWithoutExposeAnnotation() 方法会排除所以没有被Expose 标记的字段。

🐵 : 🤔 那如果只想排除某一个字段的反序列化保留序列化。类中的每个字段都要加上 Expose 注解. 🐱 : 对,没事,那就是需要这样做. 🐵 : 🤯 🐱: 这个问题我们在下面的自定义排除策略中解决。 🐱: 其实 Expose 的作用不是排除那个字段, 而是把需要的字段暴露出来,并且可以控制参与序列化或反序列化。excludeFieldsWithoutExposeAnnotation() 相当于先把所有的字段都隐藏了,然后如果字段需要序列化或者反序列化,那就使用 Expose 注解给它暴露出来,让它参与序列化或反序列化。

自定义排除策略

Expose 对那种 只想暴露少了字段,隐藏大量字段的场景适合,但是对于上面🐵 说的那如果只想排除某一个字段的反序列化保留序列化。这种场景还用 Expose 就比较麻烦了。这个时候我们可以利用Gson提供的 ExclusionStrategy 接口 自定义排除策略,通过下面的方法添加上。

//设置排除策略
setExclusionStrategies()
    
//-----更加细粒度的设置排除策略----
    
//添加序列化时排除策略   
addSerializationExclusionStrategy()
//添加反序列化时的排除策略
addDeserializationExclusionStrategy()

下面我们就通过自定义排除策略实现灵活控制忽略一个字段的序列化或者反序列化。 我们仿 Expose 注解 也写一个注解来标记字段,注解如下

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface JsonIgnore {

    /**
     *
     * @return 如果 true 就不参与 json 序列化
     */
     boolean serialize() default true;
    /**
     *
     * @return 如果 true 就不参与 json 反序列化
     */
     boolean deserialize() default true;
}

注解对应的排除策略实现

/**
 * 实现 ExclusionStrategy 接口
 */
class JsonIgnoreExclusionStrategy implements ExclusionStrategy {
    /**
     * 标记处理那种类型的排除策略
     * true:处理序列化的排除策略
     * false:处理反序列化的排除策略
     */
    private final boolean serialize;

    public JsonIgnoreExclusionStrategy(boolean serialize) {
        this.serialize = serialize;
    }

    /**
     *  根据字段属性fieldAttributes 处理是否忽略该字段,
     *  返回true就会忽略此字段
     */
    @Override
    public boolean shouldSkipField(FieldAttributes fieldAttributes) {
        //获取字段上的注解
        JsonIgnore jsonIgnore = fieldAttributes.getAnnotation(JsonIgnore.class);
        if (jsonIgnore == null) {
            return false;
        }
        //取出注解中的值返回
        return serialize ? jsonIgnore.serialize() : jsonIgnore.deserialize();
    }

    /**
     *  根据字段类型 clazz 处理是否忽略该字段,
     *  返回true就会忽略此字段
     */
    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        return false;
    }
}

把排除策略添加到Gson中

Gson gson = new GsonBuilder()
    .setExclusionStrategies()
    .addSerializationExclusionStrategy(new JsonIgnoreExclusionStrategy(true))
    .addDeserializationExclusionStrategy(new JsonIgnoreExclusionStrategy(false))
    .create();

通过上面的配置,我们想排除一个字段,就可以使用我们自定义的注解JsonIgnore 来实现了。

public class User {
    //序列化和反序列化都被忽略
    @JsonIgnore
    private Long id;
    //序列化时忽略此字段
    @JsonIgnore(serialize = true,deserialize = false)
    private String name;
    //反序列化时忽略此字段
    @JsonIgnore(serialize = false,deserialize = true)
    private int age;
    private int sex;
    private  String area;
}

测试输入结果

序列化:
{"age":20,"sex":1,"area":"shanghai"}
反序列化: 
User{id=null, name='Tom', age=0, sex=1, area='上海'}

总结

  1. 如果不考虑时序列化还是反序列化,只是想忽略某个字段,那个使用 关键词 transient 排除足以
  2. 如果只想暴露部分字段参与序列化和反序列化 ,可用使用 Expose 的方式。
  3. 再三确认Gson 自带的排除策略没法满足需求时,可以考虑自定义排除策略。