Gson用户指南


  1. 概述
  2. Gson的目标
  3. Gson性能和可扩展性
  4. Gson用户
  5. 使用Gson

概述

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

Gson可以处理任意Java对象,包括您没有源代码的预先存在的对象。

Gson的目标

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

Gson性能和可扩展性

  • 字符串:反序列化的字符串超过25MB没有任何问题(参见disabled_testStringDeserializationPerformance方法PerformanceTest
  • 大集合:
    • 序列化了140万个对象的集合(参见disabled_testLargeCollectionSerialization方法PerformanceTest
    • 反序列化87000个对象的集合(见disabled_testLargeCollectionDeserializationPerformanceTest
  • Gson 1.4将字节数组和集合的反序列化限制从80KB增加到11MB以上。

注意:删除disabled_前缀以运行这些测试。我们使用此前缀来防止每次运行JUnit测试时都运行这些测试。

Gson用户

Gson最初是为在Google内部使用而创建的,目前它在许多项目中使用。它现在被许多公共项目和公司使用。

使用Gson

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

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

在Gradle / Android中使用Gson

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

使用Gson和Maven

要将Gson与Maven2 / 3一起使用,您可以通过添加以下依赖项来使用Maven Central中提供的Gson版本:

<dependencies>
    <!--  Gson: Java to Json conversion -->
    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.8.5</version>
      <scope>compile</scope>
    </dependency>
</dependencies>

就是这样,现在您的maven项目启用了Gson。

原始例子

// 序列化
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);

对象示例

class BagOfPrimitives {
  private int value1 = 1;
  private String value2 = "abc";
  private transient int value3 = 3;
  BagOfPrimitives() {
    // no-args构造函数
  }
}

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

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

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

// 反序列化
BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class);
// ==> obj2就像obj

更精细的对象点

  • 使用私有字段是完全正确的(并推荐)。
  • 无需使用任何注释来指示要包含字段以进行序列化和反序列化。默认情况下包含当前类(以及所有超类)中的所有字段。
  • 如果字段标记为瞬态(默认情况下),则忽略该字段,并且不包括在JSON序列化或反序列化中。
  • 此实现正确处理空值。
    • 序列化时,输出中省略了空字段。
    • 在反序列化时,JSON中缺少的条目导致将对象中的相应字段设置为其默认值:对象类型为null,数字类型为零,布尔值为false。
  • 如果字段是合成的,则会被忽略,并且不包含在JSON序列化或反序列化中。
  • 与内部类,匿名类和本地类中的外部类对应的字段将被忽略,并且不包括在序列化或反序列化中。

嵌套类(包括内部类)

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

Gson还可以反序列化静态嵌套类。但是,Gson 不能自动反序列化纯内部类,因为它们的no-args构造函数还需要对反序列化时不可用的包含Object的引用。您可以通过使内部类静态或为其提供自定义InstanceCreator来解决此问题。这是一个例子:

public class A {
  public String a;

  class B {

    public String b;

    public B() {
      // No args constructor for B
    }
  }
}

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

{"b":"abc"}由于B类是一个内部类,Gson不能反序列化为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();
  }
}

以上是可能的,但不推荐。

数组示例

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 will be same as ints

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

集合示例

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 is same as ints

相当丑陋:请注意我们如何定义集合的类型。不幸的是,没有办法在Java中解决这个问题。

集合限制

Gson可以序列化任意对象的集合,但不能从中反序列化,因为用户无法指示生成的对象的类型。相反,在反序列化时,Collection必须是特定的泛型类型。这是有道理的,并且在遵循良好的Java编码实践时很少成为问题。

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

当你调用时toJson(obj),Gson调用obj.getClass()来获取有关要序列化的字段的信息。同样,您通常可以MyClass.classfromJson(json, MyClass.class)方法中传递对象。如果对象是非泛型类型,则此方法可以正常工作。但是,如果对象是泛型类型,则由于Java类型擦除而丢失通用类型信息。这是一个说明这一点的例子:

class Foo<T> {
  T value;
}
Gson gson = new Gson();
Foo<Bar> foo = new Foo<Bar>();
gson.toJson(foo); // May not serialize foo.value correctly

gson.fromJson(json, foo.getClass()); // Fails to deserialize foo.value as Bar

上面的代码无法将值解释为类型Bar,因为Gson调用list.getClass()获取其类信息,但此方法返回一个原始类,Foo.class。这意味着Gson无法知道这是一个类型的对象Foo,而不仅仅是简单的Foo。

您可以通过为泛型类型指定正确的参数化类型来解决此问题。您可以使用TypeToken该类来完成此操作。

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

gson.fromJson(json, fooType);

习惯用法fooType实际上定义了一个匿名本地内部类,其中包含一个getType()返回完全参数化类型的方法。

使用任意类型的对象序列化和反序列化集合

有时您正在处理包含混合类型的JSON数组。例如: ['hello',5,{name:'GREETINGS',source:'guest'}]

Collection包含它的等价物是:

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,并使用fromJson()Collection<MyCollectionMemberType>

仅当数组显示为顶级元素或者您可以更改将集合保持为类型的字段类型时,此方法才可用Collection<MyCollectionMemberType>

内置的序列化器和反序列化器

Gson有常用类的内置序列化器和反序列化器,其默认表示可能不合适。以下是此类的列表:

  1. java.net.URL 将它与字符串匹配 "https://github.com/google/gson/"
  2. java.net.URI 将它与字符串匹配 "/google/gson/"

自定义序列化和反序列化

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

  • Json Serializers:需要为对象定义自定义序列化

  • Json反序列化器:需要为类型定义自定义反序列化

  • 实例创建器:如果no-args构造函数可用或注册了反序列化器,则不需要

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类编写自定义序列化程序的示例。

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

Gson 在序列化期间serialize()遇到DateTime对象时调用。

编写反序列化器

下面是如何为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());
  }
}

deserialize当需要将JSON字符串片段反序列化为DateTime对象时,Gson调用

序列化器和反序列化器的更精细点

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

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

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

编写实例创建器

在反序列化Object时,Gson需要创建该类的默认实例。用于序列化和反序列化的行为良好的类应该具有无参数构造函数。

  • 公共或私人无关紧要

通常,在处理未定义无参数构造函数的库类时,需要Instance Creators

实例创建器示例

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

类型可以是相应的泛型类型

  • 调用需要特定泛型类型信息的构造函数非常有用
  • 例如,如果Id类存储了正在为其创建Id的类

参数化类型的InstanceCreator

有时,您尝试实例化的类型是参数化类型。通常,这不是问题,因为实际的实例是原始类型。这是一个例子:

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方法的type参数。这是一个例子:

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

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

紧凑型 适用于JSON输出格式的漂亮打印

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

如果要使用“漂亮打印”功能,则必须Gson使用“ 配置”来配置实例GsonBuilder。在JsonFormatter没有通过我们的公共API公开,所以客户端无法配置默认打印设置/利润率为JSON输出。目前,我们只提供JsonPrintFormatter默认行长度为80个字符,2个字符缩进和4个字符右边距的默认值。

以下是一个示例,说明如何配置Gson实例以使用默认值JsonPrintFormatter而不是JsonCompactFormatter

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

空对象支持

在Gson中实现的默认行为null是忽略对象字段。这允许更紧凑的输出格式; 但是,客户端必须为这些字段定义默认值,因为JSON格式将转换回其Java表单。

以下是配置Gson实例输出null的方法:

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

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

这是一个例子:

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

输出是:

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

版本支持

使用@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(someObject);
System.out.println(jsonOutput);
System.out.println();

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

输出是:

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

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

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

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

Java修饰符排除

默认情况下,如果将字段标记为transient,则将排除该字段。同样,如果某个字段标记为static默认情况,则会将其排除。如果要包含一些瞬态字段,则可以执行以下操作:

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

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

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

GSON的 @Expose

此功能提供了一种方法,您可以将要排除的对象的某些字段标记为序列化和反序列化为JSON。要使用此批注,必须使用创建Gson new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()。创建的Gson实例将排除类中未标注@Expose注释的所有字段。

用户定义的排除策略

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

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

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

The output is:

{"longField":1234}

JSON字段命名支持

Gson支持一些预定义的字段命名策略,将标准Java字段名称(即以小写字母开头的驼峰名称sampleFieldNameInJava)转换为Json字段名称(即sample_field_name_in_java或SampleFieldNameInJava)。有关预定义命名策略的信息,请参阅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"}

在自定义序列化器和反序列化器之间共享状态

有时您需要在自定义序列化器/反序列化器之间共享状态(请参阅此讨论)。您可以使用以下三种策略来完成此任务:

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

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

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

除了Gson的对象模型和数据绑定之外,您还可以使用Gson来读取和写入流。您还可以组合流和对象模型访问,以获得两种方法中的最佳方法。