Gson中文指南

原文地址:https://github.com/google/gson/blob/master/UserGuide.md

Gson 库中最重要的类是 Gson,你可以通过调用 new Gson() 来创建一个 Gson 对象。同时也提供一个 GsonBuilder 类来创建一个 Gson 对象,这种创建方式允许自定义多种配置信息。

Gson 对象在调用 Json 相关的操作时并不会保存任何状态信息,因此你可以在同一个项目中使用重复使用同一个 Gson 对象来对多个 Json 进行序列化和反序列化操作。

基本示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 序列化
Gson gson = new Gson();
gson.toJson(1); // ==> 1
gson.toJson("abcd"); // ==> "abcd"
gson.toJson(new Long(10)); // ==> 10
int[] values = { 1 };
gson.toJson(values); // ==> [1]

// 反序列化
int one = gson.fromJson("1", int.class);
Integer one = gson.fromJson("1", Integer.class);
Long one = gson.fromJson("1", Long.class);
Boolean false = gson.fromJson("false", Boolean.class);
String str = gson.fromJson("\"abc\"", String.class);
String[] anotherStr = gson.fromJson("[\"abc\"]", String[].class);

对象示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class BagOfPrimitives {
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;
BagOfPrimitives() {
// 无参构造方法
}
}

// 序列化
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);

// ==> json is {"value1":1,"value2":"abc"}

注意,你不能序列化一个循环引用,这样会引发无限递归。

1
2
3
// 反序列化
BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class);
// ==> obj2 is just like obj

注意事项

  • 建议使用 private 修饰属性。
  • 不需要使用任何注解来表明一个属性是否应该序列化和反序列化。默认情况下,当前类的所有属性(以及从父类继承的属性)都将被参与序列化和反序列化。
  • 如果一个属性被 transient 修饰,默认情况下该属性会被忽略掉,并且不参与 JSON 的序列化和反序列化。
  • 上面介绍的实现方式可以正确地处理 null。
  • 在序列化的时候,一个值为 null 的字段不会在输出中出现。
  • 在反序列化时,JSON中缺少的条目导致将对象中的相应字段设置为null。
  • 如果一个字段被 synthetic 修饰,则该属性会被忽略,并且不参与 JSON 的序列化和反序列化。
  • 与内部类,匿名类和局部类中的外部类对应的字段将被忽略,并且不会包含在序列化或反序列化中。

嵌套类(包括内部类)

Gson 可以非常便捷地序列化静态嵌套类。

Gson也可以反序列化静态嵌套类。然而,Gson不能自动反序列化纯内部类,因为它们的无参构造方法也需要一个对象的引用,这在反序列化时是无法获得的。你可以通过将内部类变为静态内部类,或为其提供自定义InstanceCreator来解决此问题。下面举例说明:

1
2
3
4
5
6
7
8
9
10
11
12
public class A { 
public String a;

class B {

public String b;

public B() {
// 类 B 的无参构造方法
}
}
}

注意:上面的类 B 默认情况下是无法使用 Gson 来序列化的。

由于类 B 是一个内部类,因此 Gson 无法将 {"b": "abc"} 序列化为一个 B 对象。如果将类 B 定义为静态内部类,则 Gson 就能正确地将这个字符串反序列。另一个解决方法是为类 B 编写一个自定义的 InstanceCreator :

1
2
3
4
5
6
7
8
9
public class InstanceCreatorForB implements InstanceCreator<A.B> {
private final A a;
public InstanceCreatorForB(A a) {
this.a = a;
}
public A.B createInstance(Type type) {
return a.new B();
}
}

这种方法是可行的,但是不建议这么做。

数组

1
2
3
4
5
6
7
8
9
10
11
Gson gson = new Gson();
int[] ints = {1, 2, 3, 4, 5};
String[] strings = {"abc", "def", "ghi"};

// 序列化
gson.toJson(ints); // ==> [1,2,3,4,5]
gson.toJson(strings); // ==> ["abc", "def", "ghi"]

// 反序列化
int[] ints2 = gson.fromJson("[1,2,3,4,5]", int[].class);
// ==> ints2 和 ints 一样

同时也支持多维数组,以及复杂的元素类型。

集合

1
2
3
4
5
6
7
8
9
10
Gson gson = new Gson();
Collection<Integer> ints = Lists.immutableList(1,2,3,4,5);

// 序列化
String json = gson.toJson(ints); // ==> json is [1,2,3,4,5]

// 反序列化
Type collectionType = new TypeToken<Collection<Integer>>(){}.getType();
Collection<Integer> ints2 = gson.fromJson(json, collectionType);
// ==> ints2 和 ints 相同

在反序列化的时候,注意我们如何定义集合的类型。不幸的是,虽然很丑陋,但目前没有办法在 Java 中解决这个问题。

集合的限制

Gson 可以序列化任意对象的集合,但是无法从 JSON 字符串中反序列化它们。这是因为用户无法指明结果对象的类型。反序列化的时候,集合必须是一个特定的泛型类型。在遵循良好的 Java 编码实践时很少会出现问题。

泛型类型

当你调用 toJson(obj) 的时候,Gson 会调用 obj.getClass() 来获取信息。同样的,你可以在 fromJson(json, MyClass.class) 方法中传入 MyClass.class 对象。如果该对象是一个非泛型类型,那么一切都会正常运行。然而,如果对象是一个泛型类型,由于 Java 的类型擦除机制,泛型类型信息会被丢失。下面用一个例子来阐明这个观点:

1
2
3
4
5
6
7
8
class Foo<T> {
T value;
}
Gson gson = new Gson();
Foo<Bar> foo = new Foo<Bar>();
gson.toJson(foo); // 不会正确地序列化 foo.value 字段

gson.fromJson(json, foo.getClass()); // 反序列化 foo.value 失败

上面的代码希望得到 value 的类型: Bar,然而运行的时候是失败的。因为 Gson 调用 list.getClass() 来得到类型信息,而这个方法在上面的代码中会得到一个原始类,Foo.class。也就是说 Gson 没办法知道这是一个 Foo<Bar> 类型的对象,而不仅仅是 Foo 类型的对象。

你可以通过为通用类型指定正确的参数化类型来解决此问题。你可以通过使用 TypeToken 类来做到这一点。

1
2
3
4
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.toJson(foo, fooType);

gson.fromJson(json, fooType);

上面用于获取 fooType 类型的方法实际上定义了一个匿名局部内部类,它包含一个方法 getType(),该方法返回有所的参数类型。

任意类型的对象集合

有时候你可能需要处理包含多种类型的 JSON 字符串,比如:['hello',5,{name:'GREETINGS',source:'guest'}]

相当于下面的代码:

1
2
3
4
Collection collection = new ArrayList();
collection.add("hello");
collection.add(5);
collection.add(new Event("GREETINGS", "guest"));

这里的 Event 类可以是如下的定义:

1
2
3
4
5
6
7
8
class Event {
private String name;
private String source;
private Event(String name, String source) {
this.name = name;
this.source = source;
}
}

你可以使用 Gson 来序列化这个集合,并且不需要做什么特殊处理:toJson(collection) 方法会正确地给出序列化的结果。

然而,使用 fromJson(json, Collection.class) 反序列化这个对象将不会正确地运行,因为 Gson 没办法知道如何将输入的数据映射到正确的类型中。Gson 要求你在 fromJson() 使用通用类型的集合,因此,你有三个方法解决这个问题:

  • 使用 Gson 的解析 API 来解析数组元素,然后在每一个数组元素中调用 Gson.fromJson(),这是比较推荐的方法,实例代码如下:
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
public class RawCollectionsExample {
static class Event {
private String name;
private String source;
private Event(String name, String source) {
this.name = name;
this.source = source;
}
@Override
public String toString() {
return String.format("(name=%s, source=%s)", name, source);
}
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public static void main(String[] args) {
Gson gson = new Gson();
Collection collection = new ArrayList();
collection.add("hello");
collection.add(5);
collection.add(new Event("GREETINGS", "guest"));
String json = gson.toJson(collection);
System.out.println("Using Gson.toJson() on a raw collection: " + json);
JsonParser parser = new JsonParser();
JsonArray array = parser.parse(json).getAsJsonArray();
String message = gson.fromJson(array.get(0), String.class);
int number = gson.fromJson(array.get(1), int.class);
Event event = gson.fromJson(array.get(2), Event.class);
System.out.printf("Using Gson.fromJson() to get: %s, %d, %s", message, number, event);
}
}
  • Collection.class 注册一个类型适配器,
  • MyCollectionMemberType 注册一个类型适配器,并且在 MyCollectionMemberType 中使用 fromJson()

自定义序列化器和反序列化器

有时候默认的展示方式可能不是你想要的结果,特别是在处理一些第三方库中的类的时候(比如 DateTime)。Gson 允许你注册一个自定义的序列化器和反序列化器。需要定义如下几个部分:

  • Json 序列化器:需要为一个对象自定义一个序列化
  • Json 反序列化器:需要为类型定义一个反序列化器。
  • 自定义的 InstanceCreator:不是必须的。但是如果该类没有无参构造方法的话,需要一个。
1
2
3
4
5
GsonBuilder gson = new GsonBuilder();
gson.registerTypeAdapter(MyType2.class, new MyTypeAdapter());
gson.registerTypeAdapter(MyType.class, new MySerializer());
gson.registerTypeAdapter(MyType.class, new MyDeserializer());
gson.registerTypeAdapter(MyType.class, new MyInstanceCreator());

调用 registerTypeAdapter 会检查类型适配器是否实现了这些接口中的多个接口,并对所有接口进行注册。

编写一个序列化器

下面这个例子展示了如何为 JodaTime 的 DateTime 类编写一个自定义的序列化器:

1
2
3
4
5
private class DateTimeSerializer implements JsonSerializer<DateTime> {
public JsonElement serialize(DateTime src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.toString());
}
}

在序列化的时候,如果涉及到 DateTime 类,那么就会调用这个 serialize 方法。

编写一个反序列化器

下面这个例子展示了为 JodaTime 的 DateTime 编写一个反序列化器:

1
2
3
4
5
6
private class DateTimeDeserializer implements JsonDeserializer<DateTime> {
public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return new DateTime(json.getAsJsonPrimitive().getAsString());
}
}

当你需要将一个 JSON 字符串中的一个片段反序列化为一个 DateTime 对象时,Gson 就会调用 deserialize 方法。

小细节:

通常,你要为与原始类型对应的所有类型注册一个处理器

  • 比如,假设你有一个 Id 类用来表示 id。
  • Id<T> 对所有通用类型具有相同的序列化
    • 主要是得出 id 的值。
  • 反序列化操作非常相似,但是有一点不同:
    • 需要调用 new Id(Class<T>, String),该方法返回 Id<T> 的实例。

Gson支持为此注册单个处理程序。您还可以为特定的泛型类型注册特定的处理程序(例如 Id<RequiresSpecialHandling> 需要特殊处理)。toJson()fromJson() 的 Type 参数包含通用类型信息,以帮助你为对应于相同原始类型的所有类型编写单个处理器。

编写一个 InstanceCreator

当反序列化对象时,Gson需要创建一个类的默认实例。用于序列化和反序列化的性能良好的类应该有一个无参数的构造函数。

  • 不需要关心该类的字段是 public 还是 private 修饰的。

一般情况下,当你在处理一些没有无参构造方法的类的时候,需要使用到 InstanceCreator。

InstanceCreator 实例

1
2
3
4
5
private class MoneyInstanceCreator implements InstanceCreator<Money> {
public Money createInstance(Type type) {
return new Money("1000000", CurrencyCode.USD);
}
}

参数化类型的 InstanceCreator

有时候,你想初始化的类型是参数化的类型。通常情况下,由于实际对象是原始类型,因此并没有什么问题,示例如下:

1
2
3
4
5
6
7
8
9
10
class MyList<T> extends ArrayList<T> {
}

class MyListInstanceCreator implements InstanceCreator<MyList<?>> {
@SuppressWarnings("unchecked")
public MyList<?> createInstance(Type type) {
// No need to use a parameterized list since the actual instance will have the raw type anyway.
return new MyList();
}
}

但是,有时你需要基于实际的参数化类型创建实例。在这种情况下,可以使用传递给 createInstance() 方法的类型参数。下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Id<T> {
private final Class<T> classOfId;
private final long value;
public Id(Class<T> classOfId, long value) {
this.classOfId = classOfId;
this.value = value;
}
}

class IdInstanceCreator implements InstanceCreator<Id<?>> {
public Id<?> createInstance(Type type) {
Type[] typeParameters = ((ParameterizedType)type).getActualTypeArguments();
Type idType = typeParameters[0]; // Id has only one parameterized type T
return Id.get((Class)idType, 0L);
}
}

在上面的例子中,如果没有为这个参数化类型 T 传入一个实际的类型,那么将无法创建 Id 类的实例。我们通过使用传入方法的参数 type 来解决这个问题。这个例子中的 type 对象代表 Id<Foo> 类型(调用 createInstance() 方法传入了 Id<Foo>)。因此 Id 类现在只有一个参数化类型的参数,T,我们使用 getActualTypeArgument()(这个例子中 getActualTypeArgument() 持有 Foo.class) 方法返回的类似数组中第 0 个元素

紧凑格式 VS Pretty Print

Gson 默认的 JSON 字符串输出格式是紧凑类型的 JSON 字符串格式。也就是说,输出的 JSON 字符串结构中没有任何的空格,同样的,在键/值之间、数组中的每个对象之间也都没有空格。同时,值为 null 的字段也会被忽略掉。

如果你希望使用 Pretty Print 功能,必须使用 GsonBuilder 来配置你的 Gson 对象。JsonFormatter 目前没有通过我们的公共 API 中暴露出去,因此,客户端不能够自定义 JSON 输出格式。我们只提供了一个默认的 JsonPrintFormatter 格式类,默认每行最大长度为 80 个字符,缩进长度为 2 个字符,右边距为 4 个字符。

下面的代码展示了如何配置默认的 JsonPrintFormatter 输出格式:

1
2
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String jsonOutput = gson.toJson(someObject);

空对象处理

在 Gson 中实现的默认行为是忽略空对象字段。这可以保证更紧凑的输出格式;但是,客户端必须为这些字段定义一个默认值,因为JSON格式将转换回其Java形式

为了更紧凑的输出格式,在 Gson 中,默认会将对象中 null 的字段忽略掉。如果希望显示值为 null 的字段,客户端必须为这些字段设置一个默认值。

下面的配置允许 Gson 输出 null 值得字段:

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

实例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Foo {
private final String s;
private final int i;

public Foo() {
this(null, 5);
}

public Foo(String s, int i) {
this.s = s;
this.i = i;
}
}

Gson gson = new GsonBuilder().serializeNulls().create();
Foo foo = new Foo();
String json = gson.toJson(foo);
System.out.println(json);

json = gson.toJson(null);
System.out.println(json);

输出:

1
2
{"s":null,"i":5}
null

版本支持

可以使用 @Since 注解来存储同一个对象的不同版本。该注解可以用在类和字段上,未来版本的 Gson 可能允许在方法上使用该注解。如果要使用这个功能,你必须配置你的 Gson 对象,让其忽略掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class VersionedClass {
@Since(1.1) private final String newerField;
@Since(1.0) private final String newField;
private final String field;

public VersionedClass() {
this.newerField = "newer";
this.newField = "new";
this.field = "old";
}
}

VersionedClass versionedObject = new VersionedClass();
Gson gson = new GsonBuilder().setVersion(1.0).create();
String jsonOutput = gson.toJson(someObject);
System.out.println(jsonOutput);
System.out.println();

gson = new Gson();
jsonOutput = gson.toJson(someObject);
System.out.println(jsonOutput);

输出如下:

1
2
3
{"newField":"new","field":"old"}

{"newerField":"newer","newField":"new","field":"old"}

在序列化器和反序列化器中排除字段

Gson 有多种方法来排除顶层类、字段和字段类型。下面介绍的几种方法都能排除掉字段和类。如果下面的方法不能满足你的需求,可以使用自定义序列化器和反序列化器

使用 Java 修改器

默认情况下,如果你将一个字段标记为 transient,那么它将会被排除掉,不参与序列化和反序列化操作。同样的,如果一个字段被标记为 static,那么默认情况下它也会被排除掉。如果你希望 transient 标记的字段也参与序列化和反序列化操作,那么你可以使用如下的代码:

1
2
3
4
5
import java.lang.reflect.Modifier;

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

注意:你可以在 excludeFieldsWithModifiers() 方法中传入任意数量的 Modifier 常量,比如:

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

使用 Gson 的 @Expose 注解

Gson 的 @Expose 注解提供了一种定义哪些字段参与序列化和反序列化的能力。如果要使用这个注解,需要使用 new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create() 方法来创建 Gson 对象。那么,所有没有被 @Expose 标记的字段将不参与序列化和反序列化操作。

自定义排除策略

如果上面用于排除字段和类的方法都不满足你的胃口的话,你可以尝试自己写一个排除策略,然后将其加入 Gson 中,可以参考 ExclusionStrategy 以获取更多详细信息。

下面的这个例子

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
34
35
36
37
38
39
40
41
42
43
44
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Foo {
// Field tag only annotation
}

public class SampleObjectForTest {
@Foo private final int annotatedField;
private final String stringField;
private final long longField;
private final Class<?> clazzField;

public SampleObjectForTest() {
annotatedField = 5;
stringField = "someDefaultValue";
longField = 1234;
}
}

public class MyExclusionStrategy implements ExclusionStrategy {
private final Class<?> typeToSkip;

private MyExclusionStrategy(Class<?> typeToSkip) {
this.typeToSkip = typeToSkip;
}

public boolean shouldSkipClass(Class<?> clazz) {
return (clazz == typeToSkip);
}

public boolean shouldSkipField(FieldAttributes f) {
return f.getAnnotation(Foo.class) != null;
}
}

public static void main(String[] args) {
Gson gson = new GsonBuilder()
.setExclusionStrategies(new MyExclusionStrategy(String.class))
.serializeNulls()
.create();
SampleObjectForTest src = new SampleObjectForTest();
String json = gson.toJson(src);
System.out.println(json);
}

输出如下:

1
{"longField":1234}

Json 字段命名支持

Gson 支持一些预定义的命名策略,以便将标准 Java 字段命名方式(比如,驼峰命名法则,要求变量名以小写字母开头 —— sampleFieldNameInJava)转为常见的 Json 字段名称(比如 sample_field_name_in_javaSampleFieldNameInJava)。关于预定义命名策略,请参考 FieldNamingPolicy

同时,Gson 也提供一种基于注解的方法,允许客户端使用 @SerializedName() 注解,为每一个字段自定义名称。注意,这种方法有可能会产生运行时异常。

下面的代码展示如何使用 Gson 支持的两种命名策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private class SomeObject {
@SerializedName("custom_naming") private final String someField;
private final String someOtherField;

public SomeObject(String a, String b) {
this.someField = a;
this.someOtherField = b;
}
}

SomeObject someObject = new SomeObject("first", "second");
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
String jsonRepresentation = gson.toJson(someObject);
System.out.println(jsonRepresentation);

输出如下:

1
{"custom_naming":"first","SomeOtherField":"second"}

除了从对象和 JSON 字符串中进行读写外,你也可以让 Gson 从流中进行读写操作,详情请参考 Stream

如果觉得文章对你有帮助,请我喝杯可乐吧