本次的主要内容:
一、字段过滤的几种方法
字段过滤Gson中比较常用的技巧,在处理业务逻辑时可能需要在设置的POJO中加入一些字段,但显然在序列化的过程中是不需要的,在序列化的过程中可能带来一个问题就是循环引用 ,那么在用Gson序列化之前为了不防止这样的事情发生,你不得不作另外的处理。
以一个商品分类Category 为例:
{
"id": 1,
"name": "电脑",
"children": [
{
"id": 100,
"name": "笔记本"
},
{
"id": 101,
"name": "台式机"
}
]
}
一个大分类,可以有很多小分类,那么显然我们在设计Category类时Category本身既可以是大分类,也可以是小分类。
public class Category {
public int id;
public String name;
public List children;
}
但是为了处理业务,我们还需要在子分类中保存父分类,最终会变成下面的情况。
public class Category {
public int id;
public String name;
public List children;
//因业务需要增加,但并不需要序列化
public Category parent;
}
但是上面的parent字段是因业务需要增加的,那么在序列化时并不需要,所以在序列化时就必须将其排除,那么在Gson中如何排除符合条件的字段呢?下面提供4种方法,大家可根据需要自行选择合适的方式。
基于@Expose注解
@Expose提供了两个属性,且都有默认值,开发者可以根据需要设置不同的值。
@Expose 注解从名字上就可以看出是暴露的意思,所以该注解是用于对外暴露字段的。可是我们以前用Gson的时候也没@Expose 注解,还不是正确的序列化为JSON了么?是的,所以该注解在使用new Gson() 时是不会发生作用的。该注解必须和GsonBuilder配合使用。
使用方法: 简单来说,就是在需要导出的字段上加上@Expose 注解,不导出的字段不加。
@Expose //默认反序列化和序列化都为true
@Expose(deserialize = true,serialize = true) //反序列化和序列化都生效,等价于上一条
@Expose(deserialize = true,serialize = false) //反序列化时生效,序列化时不生效
@Expose(deserialize = false,serialize = true) //序列化时生效
@Expose(deserialize = false,serialize = false) //反序列化和序列化都不生效,和不写注解一样
注:根据上面的图片可以得出,所有值为true的属性都是可以不写的(默认值是true)。
通过上面的例子加以说明:
public class Category {
@Expose
public int id;
@Expose
public String name;
@Expose
public List children;//子类别列表
//不需要序列化,所以不加 @Expose 注解,等价于 @Expose(deserialize = false,serialize = false)
public Category parent;//保存父类别
public Category() {
}
public Category(int id, String name, List children, Category parent) {
this.id = id;
this.name = name;
this.children = children;
this.parent = parent;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List getChildren() {
return children;
}
public void setChildren(List children) {
this.children = children;
}
public Category getParent() {
return parent;
}
public void setParent(Category parent) {
this.parent = parent;
}
public Category(int id, String name) {
this.id = id;
this.name = name;
}
}
在使用Gson时也不能只是简单的new Gson()了。
public static void main(String[] args) throws Exception {
Gson gson = new GsonBuilder()//基本配置
// .excludeFieldsWithoutExposeAnnotation()//个性化配置-排除没有暴露注解的字段
.create();//完成配置
Category category=new Category();
category.setId(1);
category.setName("ktz-dg");
category.setChildren(new ArrayList(){
{
add(new Category(11,"ktz-dg-child1"));
add(new Category(12,"ktz-dg-child2"));
}
});
System.out.println(gson.toJson(category));//{"id":1,"name":"ktz-dg","children":[{"id":11,"name":"ktz-dg-child1"},{"id":12,"name":"ktz-dg-child2"}]}
}
基于版本
Gson在对基于版本的字段导出提供了两个注解 @Since 和 @Until,也需要和GsonBuilder.setVersion(Double)配合使用。@Since 和 @Until都接收一个Double值。
使用方法:当前版本(GsonBuilder中设置的版本) 大于等于Since的值时该字段导出,小于Until的值时该字段导出。
public class SinceUntilSample {
@Since(4) //导出条件:>=4
public String since;
@Until(5) //导出条件:<5
public String until;
public SinceUntilSample() {
}
public SinceUntilSample(String since, String until) {
this.since = since;
this.until = until;
}
public String getSince() {
return since;
}
public void setSince(String since) {
this.since = since;
}
public String getUntil() {
return until;
}
public void setUntil(String until) {
this.until = until;
}
}
测试:
public static void sineUtilTest(double version){
SinceUntilSample sinceUntilSample = new SinceUntilSample();
sinceUntilSample.since = "since";
sinceUntilSample.until = "until";
Gson gson = new GsonBuilder().setVersion(version).create();
System.out.println(gson.toJson(sinceUntilSample));
}
public static void main(String[] args) throws Exception{
sineUtilTest(3);//{"until":"until"}
sineUtilTest(4);//{"since":"since","until":"until"}
sineUtilTest(5);//{"since":"since"}
}
注:当一个字段被两个注解同时注解时,需同时满足两个条件。
基于修饰符
什么是修饰符? public、static 、final、private、protected 这些就是。
常见的访问修饰符有public/private/protected/default(概念上的),所以这种方式是比较特殊的。
使用方式:
class ModifierSample {
final String finalField = "final";
static String staticField = "static";
public String publicField = "public";
protected String protectedField = "protected";
String defaultField = "default";
private String privateField = "private";
}
使用GsonBuilder.excludeFieldsWithModifiers构建gson,支持int型的可变参数,值由java.lang.reflect.Modifier提供,下面的程序排除了private 、final 和static三种类型的字段。
public static void main(String[] args) throws Exception{
ModifierSample modifierSample = new ModifierSample();
Gson gson = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.FINAL, Modifier.STATIC, Modifier.PRIVATE)//个性化配置-排除指定修饰符范围的字符
.create();//标注配置+个性化配置完成
System.out.println(gson.toJson(modifierSample));//{"publicField":"public","protectedField":"protected","defaultField":"default"}
}
基于策略(自定义规则)
其实用得最多的还是自定义规则,好处是功能强大、灵活,缺点是相比其它3种方法稍微麻烦些。基于策略是利用Gson提供的ExclusionStrategy接口,同样需要使用GsonBuilder,相关API有2个,分别是addSerializationExclusionStrategy 和addDeserializationExclusionStrategy,分别针对序列化和反序化。这里以序列化为例。
例如:
public class ModifierSample {
final String finalField = "final";
static String staticField = "static";
public String publicField = "public";
protected String protectedField = "protected";
String defaultField = "default";
private String privateField = "private";
}
public static void main(String[] args) throws Exception{
Gson gson = new GsonBuilder()
.addSerializationExclusionStrategy(//定制序列化排除策略
new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
// 判断要不要排除该字段,return true表示排除
if ("finalField".equals(f.getName())) return true; //按字段名称排除字段
Expose expose = f.getAnnotation(Expose.class);//获取指定注解
if (expose != null && expose.deserialize() == false) return true; //排除带有指定注解(且指定了规则)的字段
return false;//不排除字段
}
@Override
public boolean shouldSkipClass(Class> clazz) {
// 直接排除某个类,return true表示排除
return (clazz == int.class || clazz == Integer.class);//排除int或Integer类型的字段
}
})
.create();//标配+个性化配置完成
System.out.println(gson.toJson(new ModifierSample()));//默认情况下,GSON从序列化/反序列化过程中会排除瞬态和静态字段
//{"publicField":"public","protectedField":"protected","defaultField":"default","privateField":"private"}
}
二、 POJO与JSON的字段映射规则
既然叫映射规则那么说的自然是有规律的情况。
还是之前User的例子,已经去除所有注解:
User user = new User("怪盗kidou", 24);
user.emailAddress = "[email protected]";
GsonBuilder提供了FieldNamingStrategy接口,setFieldNamingPolicy()和setFieldNamingStrategy()两个方法。
默认实现
GsonBuilder.setFieldNamingPolicy 方法与Gson提供的一个枚举类FieldNamingPolicy配合使用,该枚举类提供了5种实现方式分别为:
自定义实现
GsonBuilder.setFieldNamingStrategy 方法需要与Gson提供的FieldNamingStrategy接口配合使用,用于实现POJO的字段与JSON的字段相对应。上面的FieldNamingPolicy实际上也实现了FieldNamingStrategy接口,也就是说FieldNamingPolicy也可以使用setFieldNamingStrategy的方法。
用法:
public class User {
String name;
int age;
String email;
Date birth;
String emailAddress;
public User(String name, int age, String email, Date birth, String emailAddress) {
this.name = name;
this.age = age;
this.email = email;
this.birth = birth;
this.emailAddress = emailAddress;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public User(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public User(String name, int age, String email, Date birth) {
this.name = name;
this.age = age;
this.email = email;
this.birth = birth;
}
}
public static void main(String[] args) throws Exception{
User user = new User("ktz-dg", 22);
user.emailAddress = "[email protected]";
Gson gson = new GsonBuilder()
.setFieldNamingStrategy(new FieldNamingStrategy() {//定制字段名称策略
@Override
public String translateName(Field f) {
//自定义映射规则,返回解析的字段名称
if ("name".equals(f.getName())) {
return "user-name";
}
return f.getName();//返回原字段名
}
})
.create();
System.out.println(gson.toJson(user));//{"user-name":"ktz-dg","age":22,"emailAddress":"[email protected]"}
}
注意: @SerializedName注解拥有最高优先级,在加有@SerializedName注解的字段上FieldNamingStrategy不生效!