Java 使用 Gson 操作 JSON 数据

923 阅读19分钟

1. 工具简介

Gson 用户指南

Gson 是一个 Java 库,可用于将 Java 对象转换为其 JSON 表示形式。它还可以将 JSON 字符串转换为与其等价的 Java 对象。

Gson 的目标:

  • 提供像toString()和构造函数(工厂方法)这样的易于使用的机制,实现 Java 对象和 JSON 的互相转换。
  • 允许将不可修改的预先存在的对象转换为 JSON 和从 JSON 转换。
  • 允许为对象自定义表示形式。
  • 支持任意复杂的对象。
  • 生成紧凑且易读的 JSON 输出。

使用的主要类是 Gson,可以通过调用new Gson()来创建它。还有一个类 GsonBuilder 可用,可用于创建具有各种设置的 Gson 实例,如版本控制等。

在调用 JSON 操作时,Gson 实例不维护任何状态。因此,可以自由地为多个 JSON 序列化和反序列化操作重用相同的对象。

2. 导入依赖

Gradle/Android 项目导入 Gson:

dependencies {
    implementation 'com.google.code.gson:gson:2.11.0'
}

Maven 项目到入 Gson:

<dependencies>
    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.11.0</version>
      <scope>compile</scope>
    </dependency>
</dependencies>

3. 基本使用

3.1. JSON 对象

import com.google.gson.JsonObject;

//创建 JSON 对象
JsonObject info = new JsonObject();
info.addProperty("name", "张三");
info.addProperty("age", "18");
System.out.println(info);
//{"name":"张三","age":"18"}

//JSON 对象取值
System.out.println(info.get("name").getAsString());//张三
System.out.println(info.get("age").getAsInt());//18

//JSON 对象转字符串
String str = info.toString();
//字符串转 JSON 对象
JsonObject jsonObject = JsonParser.parseString(str).getAsJsonObject();

3.2. JSON 数组

import com.google.gson.JsonArray;

//创建 JSON 数组
JsonArray array = new JsonArray();
array.add("1班");
array.add("2班");
array.add("3班");
System.out.println(array);
//["1班","2班","3班"]

//遍历取值
for (JsonElement datum : array) {
    String name = datum.getAsString();
    System.out.println(name);
}

//JSON 数组转字符串
String str = array.toString();
//字符串转 JSON 数组
JsonArray jsonArray = JsonParser.parseString(str).getAsJsonArray();
//创建 JSON 对象
JsonObject info1 = new JsonObject();
info1.addProperty("name", "张三");
info1.addProperty("age", "18");
JsonObject info2 = new JsonObject();
info2.addProperty("name", "李四");
info2.addProperty("age", "19");

//把上面创建的两个 JSON 对象加入到 JSON 数组里
JsonArray array = new JsonArray();
array.add(info1);
array.add(info2);
System.out.println(array);
//[{"name":"张三","age":"18"},{"name":"李四","age":"19"}]

//遍历获取 JSON 数组中对象的值
for (int i = 0; i < array.size(); i++) {
    JsonObject json = array.get(i).getAsJsonObject();
    System.out.println(json.get("name").getAsString());
    System.out.println(json.get("age").getAsInt());
}
//或者
for (JsonElement element : array) {
    JsonObject json = element.getAsJsonObject();
    System.out.println(json.get("name").getAsString());
    System.out.println(json.get("age").getAsInt());
}

//JSON 数组转字符串
String str = array.toString();
//字符串转 JSON 数组
JsonArray jsonArray = JsonParser.parseString(str).getAsJsonArray();

4. 使用指南

4.1. 基础示例

// 序列化
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 i = gson.fromJson("1", int.class);
Integer intObj = gson.fromJson("1", Integer.class);
Long longObj = gson.fromJson("1", Long.class);
Boolean boolObj = gson.fromJson("false", Boolean.class);
String str = gson.fromJson("\"abc\"", String.class);
String[] strArray = gson.fromJson("[\"abc\"]", String[].class);

4.2. 对象示例

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);
// ==> {"value1":1,"value2":"abc"}

// 注意,不能用循环引用序列化对象,因为这将导致无限递归。

// 反序列化
BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class); 
// ==> obj2 和 obj 一样

注意:

  • 对象使用 private 字段是完全可以的,也是推荐的。
  • 不需要使用任何注解来指示要包含用于序列化和反序列化的字段。默认情况下包括当前类(以及所有超类)中的所有字段。
  • 如果一个字段被标记为 transient,(默认情况下)它将被忽略,并且不包含在 JSON 序列化或反序列化中。
  • 在序列化时,输出中将省略值为 null 的字段。在反序列化时,JSON 中缺少的条目会导致将对象中的相应字段设置为默认值:对象类型为 null,数字类型为 0,布尔类型为 false。
  • 如果一个字段是 synthetic 的,它将被忽略,并且不包含在 JSON 序列化或反序列化中。
  • 与内部类中的外部类相对应的字段将被忽略,并且不包含在序列化或反序列化中。
  • 不包括匿名类和本地类。当序列化时,它们将被置为 null。当反序列化时,它们的 JSON 值被忽略并返回 null。将类转换为静态嵌套类,以启用它们的序列化和反序列化。

4.3. 嵌套类(包括内部类)

Gson 可以很容易地序列化静态嵌套类。

Gson 还可以反序列化静态嵌套类。然而,Gson 不能自动反序列化纯内部类,因为它们的无参数构造函数还需要对包含对象的引用,而该引用在反序列化时不可用。可以通过将内部类设置为静态或为其提供自定义实例创建器来解决此问题。下面是一个例子:

public class A {
  public String a;

  class B {

    public String b;

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

注意:B 类不能(默认情况下)用 Gson 序列化。

Gson 不能将{"b":"abc"}反序列化为 B 的实例,因为类 B 是内部类。如果它被定义为静态类 B,那么 Gson 将能够对字符串进行反序列化。另一个解决方案是为 B 编写一个自定义实例创建器。

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();
  }
}

上述方法是可行的,但不推荐使用。

4.4. 数组示例

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 相同

Gson 还支持具有任意复杂元素类型的多维数组。

4.5. 集合示例

Gson gson = new Gson();
Collection<Integer> ints = Arrays.asList(1,2,3,4,5);

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

// 反序列化
TypeToken<Collection<Integer>> collectionType = new TypeToken<Collection<Integer>>(){};
// Note: For older Gson versions it is necessary to use `collectionType.getType()` as argument below,
// 注意:对于较旧的 Gson 版本,有必要使用'collectionType.getType()'作为下面的参数,
// 然而,这不是类型安全的,必须注意为局部变量指定正确的类型
Collection<Integer> ints2 = gson.fromJson(json, collectionType);
// ==> ints2 和 ints 相同

集合的局限性:Gson 可以序列化任意对象的集合,但不能对其进行反序列化,因为用户无法指示结果对象的类型。相反,在反序列化时,集合必须具有特定的泛型类型。这是有意义的,并且在遵循良好的 Java 编码实践时很少会出现问题。

4.6. Map 示例

默认情况下,Gson 将任何java.util.Map实现序列化为 JSON 对象。因为 JSON 对象只支持字符串作为成员名,Gson 通过调用toString()将 Map 键转换为字符串,并对 null 键使用"null"

Gson gson = new Gson();
Map<String, String> stringMap = new LinkedHashMap<>();
stringMap.put("key", "value");
stringMap.put(null, "null-entry");

// 序列化
String json = gson.toJson(stringMap); // ==> {"key":"value","null":"null-entry"}

Map<Integer, Integer> intMap = new LinkedHashMap<>();
intMap.put(2, 4);
intMap.put(3, 6);

// 序列化
String json = gson.toJson(intMap); // ==> {"2":4,"3":6}

对于反序列化,Gson 使用为 Map 键类型注册的 TypeAdapter 的 read 方法。与上面的 Collection 示例类似,对于反序列化,必须使用 TypeToken 来告诉 Gson Map 键和值的类型:

Gson gson = new Gson();
TypeToken<Map<String, String>> mapType = new TypeToken<Map<String, String>>(){};
String json = "{\"key\": \"value\"}";

// 反序列化
// 注意:对于较旧的Gson版本,有必要使用'mapType.getType()'作为下面的参数,
// 然而,这不是类型安全的,必须注意为局部变量指定正确的类型
Map<String, String> stringMap = gson.fromJson(json, mapType);
// ==> stringMap is {key=value}

Gson 还支持使用复杂类型作为 Map 键。该特性可以通过GsonBuilder.enableComplexMapKeySerialization()来启用。如果启用,Gson 使用为 Map 键类型注册的TypeAdapter的 write 方法来序列化键,而不是使用 toString()。当任何键被适配器序列化为 JSON 数组或 JSON 对象时,Gson 将把完整的 Map 序列化为 JSON 数组,该数组由键值对组成(编码为 JSON 数组)。否则,如果没有一个键被序列化为 JSON 数组或 JSON 对象,Gson 将使用 JSON 对象来编码 Map:

class PersonName {
  String firstName;
  String lastName;

  PersonName(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  // ... equals and hashCode
}

Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();
Map<PersonName, Integer> complexMap = new LinkedHashMap<>();
complexMap.put(new PersonName("John", "Doe"), 30);
complexMap.put(new PersonName("Jane", "Doe"), 35);

// 序列化;复杂映射被序列化为包含键值对的 JSON 数组
String json = gson.toJson(complexMap);
// ==> [[{"firstName":"John","lastName":"Doe"},30],[{"firstName":"Jane","lastName":"Doe"},35]]

Map<String, String> stringMap = new LinkedHashMap<>();
stringMap.put("key", "value");
// 序列化;非复杂映射被序列化为常规 JSON 对象
String json = gson.toJson(stringMap); // ==> {"key":"value"}

因为 Gson 默认使用 toString() 来序列化 Map 键,这可能导致编码键的格式不正确,或者可能导致键的序列化和反序列化之间的不匹配,例如当 toString() 没有正确实现时。解决这个问题的一个方法是使用enableComplexMapKeySerialization()来确保为 Map 键类型注册的TypeAdapter被用于反序列化和序列化。如上面的示例所示,当适配器没有将任何键序列化为 JSON 数组或 JSON 对象时,Map 将按照需要序列化为常规 JSON 对象。

请注意,当将枚举反序列化为 Map 键时,如果 Gson 无法找到具有匹配name()值的枚举常量,它将返回到通过其toString()值查找枚举常量。这是为了解决上面描述的问题,但只适用于枚举常量。

4.7. 序列化和反序列化泛型类型

当调用toJson(obj)时,Gson 调用obj.getclass()来获取要序列化的字段的信息。类似地,您通常可以在fromJson(json, MyClass.class)方法中传递MyClass.class对象。如果对象是非泛型类型,这种方法可以正常工作。但是,如果对象是泛型类型,那么泛型类型信息将由于 Java 类型擦除而丢失。下面是一个例子来说明这一点:

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 反序列化为 Bar

上面的代码无法将 value 解释为 Bar 类型,因为 Gson 调用foo.getClass()来获取它的类信息,但是这个方法返回一个原始类Foo.class。这意味着 Gson 无法知道这是Foo<Bar>类型的对象,而不仅仅是普通的Foo

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

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

gson.fromJson(json, fooType);

用于获取 fooType 的习惯用法实际上定义了一个包含 getType() 方法的匿名局部内部类,该方法返回完全参数化的类型。

4.8. 序列化和反序列化具有任意类型对象的集合

有时需要处理包含混合类型的 JSON 数组。例如:['hello',5,{name:'GREETINGS',source:'guest'}],包含这个 JSON 的等价集合为:

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

其中 Event 类定义为:

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()中提供集合类型的泛化版本。所以,你有三个选择:

  1. 使用 Gson 的解析器 API(低级流解析器或 DOM 解析器JsonParser)来解析数组元素,然后对每个数组元素使用Gson.fromjson()。这是首选的方法。下面是演示如何做到这一点的示例。
  2. Collection.class注册一个类型适配器,该适配器查看每个数组成员并将它们映射到适当的对象。这种方法的缺点是它会搞砸 Gson 中其他集合类型的反序列化。
  3. MyCollectionMemberType注册一个类型适配器,并对Collection<MyCollectionMemberType>使用fromJson()

只有当数组作为顶级元素出现,或者将保存集合的字段类型更改为Collection <MyCollectionMemberType>类型时,这种方法才是实用的。

4.9. 内置序列化器和反序列化器

例如,Gson 为常用的类提供了内置的序列化器和反序列化器,这些类的默认表示形式可能不合适

  • java.net.URL与类似"https://github.com/google/gson/"这样的字符串匹配。
  • java.net.URI与类似"/google/gson/"这样的字符串匹配。

要了解更多信息,请参阅内部类 TypeAdapters。

4.10. 自定义序列化和反序列化

有时,默认表示不是你想要的。在处理库类(DateTime等)时经常出现这种情况。Gson 允许您注册自己的自定义序列化器和反序列化器。这是通过定义两个部分来实现的:

  • JSON 序列化器:需要为对象定义自定义序列化。
  • JSON 反序列化器:需要为类型定义自定义反序列化。
  • 实例创建者:如果无参数构造函数可用或注册了反序列化器,则不需要实例创建者。
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调用检查。

  1. 如果类型适配器实现了一个以上的接口,在这种情况下,它将为所有这些接口注册适配器。
  2. 如果类型适配器是 Object 类或 JsonElement 或其任何子类的,在这种情况下,它会抛出 IllegalArgumentException,因为不支持覆盖这些类型的内置适配器。

4.10.1. 编写序列化器

下面是一个如何为 JodaTime DateTime 类编写自定义序列化器的示例。

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

Gson 在序列化过程中遇到 DateTime 对象时调用 serialize()。

4.10.2. 编写反序列化器

下面是如何为 JodaTime DateTime 类编写自定义反序列化器的示例。

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 调用反序列化。

4.10.3. 序列化器和反序列化器的优点

通常,你希望为与原始类型对应的所有泛型类型注册单个处理程序。

  • 例如,假设你有一个 Id 类用于 Id 表示/转换(即内部表示与外部表示)。
  • Id<T>类型,对所有泛型类型具有相同的序列化。
  • 反序列化非常相似,但并不完全相同。需要调用new Id(Class<T>, String),返回Id<T>的实例。

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

4.11. 编写实例创建器

在反序列化对象时,Gson 需要创建类的默认实例。用于序列化和反序列化的行为良好的类应该具有无参数构造函数。不管是public 的还是 private 的。通常,当您处理没有定义无参数构造函数的库类时,需要实例创建器。

4.11.1. 实例创建器示例

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

类型可以是相应的泛型类型。对于调用需要特定泛型类型信息的构造函数非常有用。例如,如果 Id 类存储正在为其创建 Id 的类。

4.11.2. 参数化类型的 InstanceCreator

有时,尝试实例化的类型是参数化类型。一般来说,这不是问题,因为实际实例是原始类型。下面是一个例子:

class MyList<T> extends ArrayList<T> {
}

class MyListInstanceCreator implements InstanceCreator<MyList<?>> {
  @SuppressWarnings("unchecked")
  public MyList<?> createInstance(Type type) {
    // 不需要使用参数化列表,因为实际实例无论如何都会具有原始类型。
    return new MyList();
  }
}

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

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 只有一个参数化类型 T
    return new Id((Class)idType, 0L);
  }
}

在上面的示例中,如果不实际传入参数化类型的实际类型,就不能创建 Id 类的实例。我们通过使用传递的方法参数type来解决这个问题。本例中的类型对象是Id<Foo>的 Java 参数化类型表示,其中实际实例应该绑定到Id<Foo>。由于 Id 类只有一个参数化类型参数 T,因此我们使用getActualTypeArgument()返回的类型数组的第 0 个元素,在本例中它将保存Foo.class

4.12. JSON 输出格式的紧凑和美观打印

Gson 提供的默认 JSON 输出是一种紧凑的 JSON 格式。这意味着在输出 JSON 结构中不会有任何空白。因此,在 JSON 输出中的字段名与其值、对象字段和数组中的对象之间不会有空白。同样,"null"字段将在输出中被忽略(注意:null 值仍将包含在对象的集合/数组中)。有关配置 Gson 以输出所有空值的信息,请参阅 Null 对象支持部分。

如果你想使用“美观打印”功能,你必须使用GsonBuilder配置你的Gson实例。JsonFormatter 没有通过公共 API 公开,因此客户端无法为 JSON 输出配置默认的打印设置/边距。目前,我们只提供一个默认的 JsonPrintFormatter,默认行长为 80 个字符,2 个字符缩进,4 个字符右距。

下面的例子展示了如何配置一个 Gson 实例来使用默认的 JsonPrintFormatter 而不是 JsonCompactFormatter:

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

4.13. Null 对象支持

在 Gson 中实现的默认行为是忽略 null 对象字段。这允许更紧凑的输出格式;但是,在将 JSON 格式转换回 Java 格式时,客户端必须为这些字段定义一个默认值。下面是如何配置一个 Gson 实例输出 null:

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

注意:当用 Gson 序列化 null 时,它会在 JsonElement 结构中添加一个 JsonNull 元素。因此,该对象可用于自定义序列化/反序列化。给个例子:

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); // {"s":null,"i":5}

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

4.14. 版本支持

通过使用 @Since 注释,可以维护同一对象的多个版本。这个注释可以用在类、字段和(在将来的版本中)方法上。为了利用这个特性,必须将 Gson 实例配置为忽略任何大于某个版本号的字段/对象。如果没有在 Gson 实例上设置版本,那么它将序列化和反序列化所有字段和类,而不管版本如何。

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(versionedObject);
System.out.println(jsonOutput); // {"newField":"new","field":"old"}

gson = new Gson();
jsonOutput = gson.toJson(versionedObject);
System.out.println(jsonOutput); // {"newerField":"newer","newField":"new","field":"old"}

4.15. 从序列化和反序列化中排除字段

Gson 支持多种机制来排除顶级类、字段和字段类型。下面是允许字段和类排除的可插入机制。如果下面的机制都不能满足你的需求,那么你还可以使用自定义序列化器和反序列化器。

4.15.1. Java 修饰符排除

默认情况下,如果您将一个字段标记为transient,它将被排除。同样,如果一个字段被标记为static,那么默认情况下它将被排除。如果你想包含一些瞬态字段,那么你可以这样做:

import java.lang.reflect.Modifier;
Gson gson = new GsonBuilder()
    .excludeFieldsWithModifiers(Modifier.STATIC)
    .create();

注意:你可以给excludeFieldsWithModifiers方法提供任意数量的 Modifier 常量。例如:

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

4.15.2. Gson 的 @Expose

该特性提供了一种方法,可以将对象的某些字段标记为排除字段,以便将其序列化和反序列化为 JSON。要使用这个注释,你必须使用new GsonBuilder(). excludeFieldsWithoutExposeAnnotation().create()来创建 Gson。创建的 Gson 实例将排除类中没有使用 @Expose 注释标记的所有字段。

4.15.3. 用户自定义排除策略

如果上述排除字段和类类型的机制不适合你,那么你可以编写自己的排除策略并将其插入 Gson。有关更多信息,请参阅 ExclusionStrategy JavaDoc。

下面的示例展示了如何排除用特定 @Foo 注释标记的字段,并排除类 String 的顶级类型(或声明的字段类型)。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Foo {
  // 仅字段标记注解
}

public class SampleObjectForTest {
  @Foo
  private final int annotatedField;
  private final String stringField;
  private final long longField;

  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); // {"longField":1234}
}

4.16. JSON 字段命名支持

Gson 支持一些预定义的字段命名策略,将标准 Java 字段名(即,以小写字母开头的驼形大小写名称 --- sampleFieldNameInJava)转换为 JSON 字段名(即sample_field_name_in_javasampleFieldNameInJava)。有关预定义命名策略的信息,请参阅 FieldNamingPolicy 类。

它还有一个基于注释的策略,允许客户端在每个字段的基础上定义自定义名称。注意,基于注释的策略具有字段名验证,如果提供了无效的字段名作为注释值,将引发“运行时”异常。

下面是如何使用 Gson 命名策略特性的示例:

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); // {"custom_naming":"first","SomeOtherField":"second"}

如果需要自定义命名策略(参见本讨论),可以使用 @SerializedName 注释。

4.17. 跨自定义序列化器和反序列化器共享状态

有时需要在自定义序列化器/反序列化器之间共享状态(参见本讨论)。你可以使用以下三种策略来做到这一点:

  1. 在静态字段中存储共享状态。
  2. 将序列化器/反序列化器声明为父类型的内部类,并使用父类型的实例字段来存储共享状态。
  3. 使用 Java ThreadLocal。

1 和 2 不是线程安全的选项,但 3 是。

4.18. 流

除了 Gson 的对象模型和数据绑定之外,还可以使用 Gson 对流进行读写。您还可以结合流和对象模型访问来获得这两种方法的最佳效果。