Gson使用简介

概述

Gson是啥呢,它是一个能把Java对象转化成JSON表现形式的一个库。并且他也能把一个JSON 字符串转换成等价的Java对象。Gson 是Google 开源出来的一个开源项目, 目前代码托管在github上。

据说他很吊,能操作任何的java对象,甚至是没有源码的已经存在的Java对象。

Gson 要解决的问题

  1. 提供一种像toString()和构造方法的很简单的机制,来实现Java 对象和Json之间的互相转换。
  2. 允许已经存在的无法改变的对象,转换成Json,或者Json转换成已存在的对象。
  3. 允许自定义对象的表现形式
  4. 支持任意的复杂对象
  5. 能够生成压缩的和人可读的Json的字符串输出。

Gson 的性能和可扩展性

下面这些性能的测试结果是在普通电脑(dual opteron, 8GB RAM, 64-bit ubuntu)做的,在测试时电脑还干了别的活,不信的话你可以自己试试 PerformanceTest

  • Strings : 据说反序列化一个大于25MB的String是完全无压力的
  • 大容量的集合:序列化一个140万个对象的集合,反序列化一个87,000个对象的集合
  • Gson 1.4将字节数组和集合的反序列化限制从80KB提高到11 MB以上。

Gson 的用户

Gson 最初的时候是Google的内部库,后来开源,现在很多项目再用。 点我,我们都在用Gson

使用Gson

在这个库中你能用到得最主要的类即使 Gson ,使用的方式也很简单,直接通过调用无参的构造方法就可以了 new Gson() 当然本库也提供Builder的方式创建Gson对象,这个类叫做GsonBuilder 用这种方式创建的Gson实例,已经有了很多默认配置。

Gson实例调用Json操作时不维护任何状态。So,你能复用Gson对象,说白了就是你new一个Gson对象之后,可以用这个对象操作,任何的序列化和反序列化,不用再次创建Gson对象。

Using Gson with Maven

要在Maven2 / 3中使用Gson,可以通过添加以下依赖关系使用Maven Central中提供的Gson版本:

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

好了,现在你的maven项目就使用了Gson了。

Gson 操作基本数据类型的例子

// Serialization
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]

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

Gson 操作对象

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

// Serialization
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);  

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

注意,你不能使用循环来序列化对象,否则会造成无限递归···

Gson所处理的对象的几点说明

  • 用private 字段比较好,推荐把成员变量都声明称private的
  • 没有必要用注解(注释)指明某个字段是否会被序列化或者反序列化,所有包含在当前类(包括父类)中的丢按都应该默认被序列化或者反序列化
  • 如果某个字段被 transient 这个Java关键词修饰,默认情况下,它是被忽略的,也就是说,他不应该被序列化或者反序列化
  • 下面的实现方式能够正确的处理null

    • 当序列化的时候,值为null的字段,会在输出中被跳过,也就是说如果对象的某个字段为null,在序列化的时候是不会输出到Json字符串中的。
    • 当反序列化的时候,如果Mode类的,某个字段在Json字符串中找不到对应的值,就会被赋值为null
  • 如果一个字段是 synthetic 的,他会被忽视,也即是不应该被序列化或者反序列化
  • 内部类(或者anonymous class(匿名类),或者local class(局部类,可以理解为在方法内部声明的类))的某个字段和外部类的某个字段一样的话,他会被忽视,也即是不应该被序列化或者反序列化

嵌套类 包含内部类

Gson 能够狠简单的处理 静态 嵌套类。

Gson也能反序列化 静态 的嵌套类。但是Gson不能自动的反序列化一个纯内部类(没有static 声明),因为这个内部类的无参构造方法要被外部类对象引用,但是这时候外部类对象还不存在,你可用通过声明内部类为static的方式来使用内部类,也可以为内部类创建一个InsuranceCreator。看下边的例子吧:

public class A {
    public String a;

    class B {

        public String b;

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

默认情况下,Gson是不会序列化B类的。

因为上面的B类是一个内部类,所以Gson不能把 {"b":"abc"} 这个Json字符串,序列化成B的实例对象,但是如果B类被声明成了static的,那么就可以了。另一种解决方案是给B类整一个 instance creator ( 但是这种方式不被推荐 )

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 gson = new Gson();
int[] ints = {1, 2, 3, 4, 5};
String[] strings = {"abc", "def", "ghi"};

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

// Deserialization
int[] ints2 = gson.fromJson("[1,2,3,4,5]", int[].class); 
// ==> ints2 will be same as ints

Gson也支持多维数组,任意复杂的元素类型

集合使用Gson

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

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

(Deserialization)
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() 来获取字段信息。同样的你也可以在调用fromJson(json,MyClass.class)的时候把MyClass.class 传入。在不使用泛型的时候,这种方式运用起来完全没问题。但是,如果obj是一个泛型对象。Java的Type Erasure 会导致泛型信息的丢失。看下边的例子:

    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

上面的代码不能把Json解析成Bar因为 当调用foo.getClass()的获取Foo类的信息的时候,但是返回的是Foo的原生的类类型,即:Foo.class 。也即是说Gson无法获取到这个对象是Foo类型的,他只能获取到简单的Foo的类型。

你可以通过制定参数的方式来解决这个泛型的类型问题。通过 TypeToken 类

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

gson.fromJson(json, fooType);

可以看到通过创建一个TypeToken的匿名局部内部类的对象,并调用他的getType()方法,来获取,这个泛型对象的完全的参数化类型。

序列化和反序列化包含对象(对象中包含任意类型)的集合

有时候你会处理包含混合类型的Json字符串:如下:

[ ‘hello’,5,{name:’GREETINGS’,source:’guest’} ]

它等价于

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)就能得到你想要的Json字符串了。

但是,你想用fromJson(json,Collection.class)反序列化的时候,就不能正常运行了,因为Gson不能映射输入的Json字符串到,Collection.class 类型。这是Gson需要你在调用fromJson的时候提供一个,可以用的collection的类型。有三种方式供选择:

  1. 使用Gson解析器API(低级流解析器或DOM解析器JsonParser)解析数组(Json数组)元素,然后对数组的每个元素使用Gson.fromJson()。这是首选的方法。
  2. 给Collection.class 注册一个类型适配器。用来查找数组(指的Json数组)中的每个元素,然后找到他们适合的对象,适配他们。这种方法的缺点是,他会弄乱别的集合类型的反序列化。
  3. 为你的泛型类,指定注册一个type adapter。假设你的泛型类为MyCollectionMemberType,(原文如下:翻译不了了,也理解不了)

只有当数组显示为顶级元素或者您可以将保存集合的字段类型更改为Collection <MyCollection MemberType>时,此方法才是实用的。

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

默认情况下Gson使用的是内置的序列化器和反序列化器,但是用这些序列化器和反序列化器,构造出来的Json字符串,或者,反序列化出来的对象并不是我们想要的样式

例如下边的两个列子:

  • java.net.URL 能够自动匹配(通过内置的反序列化器)这样的字符串: “http://code.google.com/p/google-gson/”.
  • java.net.URI 能够自动匹配这样的字符串 “/p/google-gson/”.

你可以在这里找到一些常用类的源码。

自定义序列化和反序列化

有时候默认的配置不符合你的要求,一般在处理类库中的类的时候会遇到,例如(DateTime ,等等。。。),Gson允许你注册自定义的serializer和deserializer,这需要通过两部分来实现:

  • Json Serialiers: 需要为一个对象定义自定义的序列化过程
  • Json Deserializers: 需要为一个类型定义自定义的反序列化过程。
  • Instance Creators: 如果无参构造函数可用,或者反序列化解析器已经注册,那么它不是必须提供的
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 ,这个方法的时候,会检查type adapter是否实现了多个接口,如果是,注册所有的接口。

实现一个Serializer

下面是一个列子来为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()方法。

实现一个Deserializer

下边是个列子,来为JodaTime类库中的DateTime类实现一个自定义的deserializer:

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

Gson在需要将JSON字符串反序列化为DateTime对象时调用deserialize。

使用Serializers 和 Deserializers的几个细节

你经常需要为一个原始类型的泛型注册一个单一的处理器。

  • 例如,假设你有一个“Id”类来代表或者转换Id(例如内部代表和外部代表)
  • Id<T>类型对于所有的泛型都具有相同的序列化过程:输出本质代表的Id值。
  • 反序列过程很类似但是不完全一样:需要调用“new Id(Class<T>, String)”来返回一个Id<T>实例。

Gson支持注册一个处理程序,你也可以为指定的泛型注册指定的处理程序。toJson和fromJson的Type参数包含了泛型信息以帮助你写出一个可以将所有的泛型与同一个原始类型对应起来的处理程序。

写一个实例创建器

在反序列化一个对象时,Gson需要创建一个类的实例。在序列化/反序列化时具有良好表现的类是指这个类拥有一个无参构造函数。通常,当处理一个类库中没有无参构造函数的类时,需要使用实例创建器。

  • 不要在意是public 还是 private

通常,当处理一个类库中没有无参构造函数的类时,需要使用实例创建器。

实例创建器示例

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

类型可以是相应的通用类型

  • 需要有构造函数
  • 例如,要存储Id类,则要为其创建Id的类(什么鬼?)

参数化类型的实例创建器

有时候要实例化的类型会是一个参数化类型。总的来说,由于真正的实例是一个原始类型,所以这不是什么问题。下面是一个示例:

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方法。下面是一个例子:

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对象可以看做是Id<Foo>的Java参数化类型的表示,相应的实例应该被绑定到Id<Foo>。由于类Id只有一个参数化类型的参数T,我们使用getActualTypeArgument()返回的类型数组的第0个元素,在这个例子中就是Foo.class。

紧凑的输出 VS 优美的输出

Gson中Json默认的输出是紧凑的JSON格式。也就是说在JSON中没有多余的空白符。所以在JSON的输出中字段名和字段值之间、字段之间、数组元素之间是没有空白的。另外,null字段不会被输出(注意:在集合和数组对象中null会被保留的)。

如果要输出的优美些,你需要使用GsonBuilder对Gson的实例进行配置。JsonFormatter不存在于公有API中,所以客户端无法配置默认的输出设置。现在我们只提供了JsonPrintFormatter,其默认情况下每行80个字符,缩进使用2个字符,右边距是4个字符。

下面的示例展示了如何让Gson实例使用JsonPrintFormatter,而不是使用默认的JsonCompactFormatter。

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

空对象

在Gson的默认实现中,null对象是被忽略的。这可以让输出格式(既可以认为是序列化的结果)更加紧密;不过客户端必须为其定义一个默认的值,以使得JSON能够正常的反序列化。

如果要让Gson实例可以序列化null,可以:

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

注意,当序列化null的时,会在JsonElement结构中添加一个JsonNull元素。因此,我们可以可以在自定义的序列化器/反序列化器中使用这个对象(gson)。

下面是一个例子:

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 Modifier Exclusion

默认情况下,如果将一个字段声明为transient,这个字段就会被排除。另外,如果一个字段被声明为static,默认情况下这个字段也会被排除。如果要包含某些声明为transient的字段,你可以这样做:

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

使用@Expose字段排除

这个特性允许你在类中标记特定的字段使其在序列化/反序列化中不被排除/被排除。要使用这个标注,你应该使用new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()创建Gson。Gson实例会排除类中所有没被@Expose标注的字段。

用户定义排除策略

如果上面的排除方法无法满足需求,你也可以自定义自己的排除策略。更多内容,可以参考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);
}

输出是:

{"longField":1234}

JSON字段命名的支持

Gson的一些预定义的字段命名策略,可以将标准的Java字段名称(也就是驼峰命名法,例如sampleFieldNameInJava)转换成一个Json的字段名(也就是sample_field_name_in_java或者SampleFieldNameInJava)。更多信息,可以参考FieldNamingPolicy。

Gson也有一个基于标注的策略让客户端自定义字段的名称。这个策略下,如果提供了一个非法的字段名作为标注的值,会使Gson抛出Runtime异常。

下面的示例展示了如何使用这两种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标注。

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

有时你会需要在序列化器和反序列化器之间共享状态,你可以使用下面的三个方法达到目的:

  • 在一个静态字段中存储共享状态
  • 将序列化/反序列化器声明为一个父类型的内部类,然后使用父类型的实例的字段存储共享状态
  • 使用Java中的ThreadLocal

前两种方法不是线程安全的,第三种是。

除了Gson的对象模型和数据绑定,你可以使用Gson从流中读取数据或者向流中写入数据。具体信息,可以参考Streaming

设计Gson的问题

我们在设计Gson时遇到的问题的讨论,可以查看Gson设计文档。 其中还包括Gson与可用于Json转换的其他Java库的比较。

Gson未来会增加的功能

如果您想增加新功能,请参阅项目网站下的Issues section