Leo's Blog

Gson 正确打开姿势(二)

GsonBuilder 初探

Gson 类提供的API满足我们大部分的使用场景,但是有时我们需要更强大的功能,这时候就需要使用 GsonBuilder。
GsonBuilder,见名知意,它用于构建Gson的实例,用来改变Gson的默认配置。

GsonBuilder 用法

1
2
3
4
// 返回自定义的Gson实例。
Gson gson = new GsonBuilder()
// 各种配置
.creat();

我们来看一个示例:

1
2
3
4
5
public class User{
public String name;
public int age;
public String email;
}

1
2
3
Gson gson = new Gson();
User user = new User("Leo",28);
System.out.println(gson.toJson(user)); //{"name":"Leo","age":28}

从结果可以看出email字段,在输出的json串中没有体现。如果我们需要通过日志输出查看User中所有的字段。

1
2
3
4
5
Gson gson = new GsonBuilder()
.serializeNulls()
.create();
User user = new User("Leo",28);
System.out.println(gson.toJson(user)); //{"name":"Leo","age":28,"email":null}

再来看一些高级点的功能,格式化输出,日期时间等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Gson gson = new GsonBuilder()
//序列化null
.serializeNulls()
// 设置日期时间格式,另有2个重载方法
// 在序列化和反序化时均生效
.setDateFormat("yyyy-MM-dd")
// 禁此序列化内部类
.disableInnerClassSerialization()
//生成不可执行的Json, 多了 )]}' 这4个字符)
.generateNonExecutableJson()
//禁止转义html标签
.disableHtmlEscaping()
//格式化输出
.setPrettyPrinting()
.create();

字段过滤

在项目开发过程中,我们有些字段是不需要进行序列化的,那么在序列化时,我们就要将这些字段过滤,让我们来看一下 Gson 如何解决的这个问题,它提供四种方式:

基于 @Expose 注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Expose {
/**
* If {@code true}, the field marked with this annotation is written out in the JSON while
* serializing. If {@code false}, the field marked with this annotation is skipped from the
* serialized output. Defaults to {@code true}.
* @since 1.4
*/
public boolean serialize() default true;
/**
* If {@code true}, the field marked with this annotation is deserialized from the JSON.
* If {@code false}, the field marked with this annotation is skipped during deserialization.
* Defaults to {@code true}.
* @since 1.4
*/
public boolean deserialize() default true;
}

看源码注释,我们得知: @Expose 有两个属性 serialize 和 deserialize
,默认值为 true。
|属性|true|false|
|–|–|
|serialize|序列化|不序列化|
|deserialize|反序列化|不反序列化|

1
2
3
4
5
6
7
8
9
10
public class User{
@Expose
public String name;// 序列化和反序列化都生效
@Expose(deserialize=true;serialize=false)
public int age;// 序列化不生效,反序列化生效
@Expose(deserialize=false;serialize=true)
public String email; // 序列化生效,反序列化不生效
@Expose(deserialize=false;serialize=false) : 可以不写
public String des; // 序列化和反序列化都不生效
}

@Expose 注解必须配合 GsonBuilder 使用。

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

基于版本 setVersion()

Gson 另外提供了两个注解 @Since 和 @Util ,配合setVersion(double version) 使用。当然我们还是通过代码来演示:

  • 创建Gson实例 调用 setVersion(),设置版本为1.0
    1
    2
    3
    Gson gson = new GsonBuilder()
    .setVersion(1.0)
    .create();
1
2
3
4
5
6
public class User {
@Since(1.0) private String emailAddress;
@Since(1.1) private Address address;
@Util(1.0) private String password;
@Util(1.1) private String username;
}
  • @Since
    当前版本大于或等于@Since标注版本时,字段可以序列化和反序列化。
    上面代码 emailAddress 可以序列化和反序列化,address 不可以。

  • @Util
    当前版本小于@Util标注版本时,字段可以序列化和反序列化。
    上面代码 username 可以序列化和反序列化,password 不可以。

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

基于访问修饰符

常用的修饰符 public、static、final、private、protected等等。
我们可以通过 excludeFieldsWithModifiers(int… modifiers)来过滤字段。

1
2
3
4
5
6
7
8
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提供,下面的程序排除了privateField 、 finalField 和staticField 三个字段。

1
2
3
Gson gson = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.FINAL, Modifier.STATIC, Modifier.PRIVATE)
.create();

自定义规则

自定义规则相对麻烦一些,但是更强大,更灵活。
Gson提供了ExclusionStrategy接口,同样需要使用GsonBuilder,相关API 分别是addSerializationExclusionStrategy(序列化)和addDeserializationExclusionStrategy(反序列化)。下面我们以序列化为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
}
})
.create();

序列化命名自定义

涉及到的API :

  1. GsonBuilder : setFieldNamingPolicy() 和 setFieldNamingStrategy()
  2. FieldNamingStrategy 接口
  3. 实现了FieldNamingStrategy 的枚举类 FieldNamingPolicy

Gson 提供的默认实现

setFieldNamingPolicy() 结合 FieldNamingPolicy

1
2
3
Gson gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE) .create();
Log.e("GsonDemo", gson.toJson(repository));

FieldNamingPolicy 输出结果(emailAddress字段为例)
IDENTITY {“emailAddress”:”leo@example.com”}
LOWER_CASE_WITH_DASHES {“email-address”:”leo@example.com”}
LOWER_CASE_WITH_UNDERSCORES {“email_address”:”leo@example.com”}
UPPER_CAMEL_CASE {“EmailAddress”:”leo@example.com”}
UPPER_CAMEL_CASE_WITH_SPACES {“Email Address”:”leo@example.com”}

自定义规则

setFieldNamingStrategy() 结合 FieldNamingStrategy

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

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

随心所欲玩转序列化和反序列化

TypeAdapter

TypeAdapter 是Gson2.1提供的一个抽象类,用于POJO的序列化和反序列化。主要重写两个方法 write(JsonWriter out, T value) 和 read(JsonReader in)

1
2
3
4
public abstract class TypeAdapter<T> {
public abstract void write(JsonWriter out, T value) throws IOException;
public abstract T read(JsonReader in) throws IOException;
}

使用示例:

1
2
3
4
5
User user = new User("leo", 28,"leo@example.com");
Gson gson = new GsonBuilder()
//为User注册TypeAdapter
.registerTypeAdapter(User.class, new UserTypeAdapter())
.create();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class UserTypeAdapter extends TypeAdapter<User> {
@Override
public void write(JsonWriter out, User value) throws IOException{
out.beginObject();
out.name("name").value(value.name);
out.name("age").value(value.age);
out.name("email").value(value.email);
out.endObject();
}
@Override
public User read(JsonReader in) throws IOException {
User user = new User();
in.beginObject();
while (in.hasNext()) {
switch (in.nextName()) {
case "name":
user.name = in.nextString();
break;
case "age":
user.age = in.nextInt();
break;
case "email":
case "email_address":
case "emailAddress":
user.email = in.nextString();
break;
}
}
in.endObject();
return user;
}
}

当我们为 User.class 注册了 TypeAdapter 之后,操作User.class的 那些@SerializedName、FieldNamingStrategy、@Since、@Until、@Expose,q全部废废,只会调用我们实现的UserTypeAdapter.write(JsonWriter, User) 方法,我们想怎么玩就怎么玩。

当然上面的序列化和反序列化的操作是有缺点的,如果我们想要对多个POJO随心所欲的操作,那么在创建 Gson 实例就要注册很多个 TypeAdapter ,追求代码洁癖的人会疯的。解决这个问题就需要用到 @JsonAdapter 注解。

@JsonAdapter

前面提到 @JsonAdapter 可以解决注册多个 TypeAdapter 的问题~ 它用来标注POJO类,接收一个参数,参数必须是 TypeAdapter,JsonSerializer或 JsonDeserializer 中的一个。

使用方法(以User为例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@JsonAdapter(UserTypeAdapter.class)
public class User{
public String name;
public int age;
@SerializedName(value="emialAddress")
public String email;
public User(){}
public User(String name,int age,String email){
this.name = name;
this.age = age;
this.email = email;
}
}

使用时不用再 GsonBuilder 去注册 UserTypeAdapter 了。

注:

  • @JsonAdapter 仅支持 TypeAdapter 或 TypeAdapterFactory。
  • @JsonAdapter 的优先级比 GsonBuilder.registerTypeAdapter 的优先级高。

JsonDeserializer 与 JsonSerializer

那么问题来了,我们只想控制序列化过程或者反序列化过程,比如我们后端经常在我们需要double类型数据时,返回空串””,导致我们数据异常,引起崩溃。肿么办?

同样见名知意, JsonDeserializer 可以用控制反序列化,JsonSerializer 控制序列化。

JsonDeserializer 反序列化操作

1
2
3
4
5
6
7
8
9
10
11
12
13
Gson gson = new GsonBuilder()
.registerTypeAdapter(Double.class, new JsonDeserializer<Double>(){
@Override
public Double deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException{
// 处理脏数据,返回一个默认值
try{
return json.getAsDouble();
}catch(NumberFormatException e){
return -1.0;
}
}
})
.create();

JsonSerializer

1
2
3
4
5
6
7
8
9
10
11
12
JsonSerializer<Number> jsonSerializer = new JsonSerializer<Number>() {
@Override
public JsonElement serialize(Number src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(String.valueOf(src));
}
};
Gson gson = new GsonBuilder(){
.registerTypeAdapter(Integer.class, jsonSerializer)
.registerTypeAdapter(Double.class, jsonSerializer)
.registerTypeAdapter(Long.class, jsonSerializer)
.registerTypeAdapter(Float.class, jsonSerializer)
.create();

注: registerTypeAdapter 必须使用包装类型,所以 int.class ,long.class,double.class 是不可以的。