泛型:
- https://juejin.im/post/5b614848e51d45355d51f792#heading-7
- https://www.jianshu.com/p/5179ede4c4cf
Gson使用指南
- 概览
- Gson的目标
- Gson性能和可扩展性
- Gson用户
- 使用Gson(这里指的是android)
- 在Gradle/Android中使用Gson
- 例子
- 对象实例
- 对象细节
- 嵌套类(包括内部类)
- 数组示例
- 集合示例
- 序列化和反序列化泛型类型
- 任意类型对象集合的序列化和反序列化
- 内置的序列化器和反序列化器
- 自定义序列化和反序列化
- 编写序列化程序
- 编写反序列化程序
- 编写实例创建器
- 紧凑型 vs JSON的漂亮输出格式
- 空对象支持
- 版本控制支持
- 从序列化和反序列化中排除字段
- JSON字段命名支持
- 在自定义序列化和反序列化之间共享状态
- 流
- 设计Gson的问题
- Gson的未来改进
概览
Gson是一个Java库,可用于将Java对象转换为其JSON表示。还可以用于将JSON字符串转换为等效的Java对象。
Gson可以处理任意Java对象,包括没有在代码中预设的对象。
Gson的目标
- 提供易于使用的机制,如toString()构造函数(工厂方法),将Java转换为JSON,反之亦然
- 允许将预先存在的不可修改对象转换为JSON或从JSON转换
- 允许对象的自定义表示
- 支持任意复杂的对象
- 生成紧凑且可读的JSON输出
Gson性能和可扩展性
Gson用户
Gson的使用
要使用的主要类是Gson,可以通过调用创建的new Gson()以及GsonBuilder类用于创建具有各种设置(如版本控制等)的Gson实例。
在调用Json操作时,Gson实例不维护任何状态。因此,您可以重用相同的Gson对象自由地为多个Json进行序列化和反序列化操作。
在Gradle/Android中使用Gson
dependencies {
implementation 'com.google.code.gson:gson:2.8.5'
}
具体版本请参照网站
https://github.com/google/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{
// 无参构造
}
}
// 序列化
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
// ==> json 内容是 {"value1":1,"value2":"abc"}
// 反序列化
BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class);
// ==> obj2 是一个像 obj 的对象
对象细节:
- 最好使用private修饰字段
- 无需使用任何注释来指示要进行序列化和反序列化的字段。默认情况下当前类(包括所有从父类继承)的所有字段都会被包含
- 如果一个字段被transient修饰了,则在序列化和反序列化的过程会被忽略
- Gson实现会正确处理空值
- 当序列化时,一个为空的字段将被忽略
- 当反序列化时,在反序列化时,JSON中缺少的条目导致将对象中的相应字段设置为其默认值:对象类型为null,数字类型为零,布尔值为false。
- 如果字段是合成的,则会被忽略,并且不包含在JSON序列化或反序列化中。
- 内部类,匿名类和本地类的字段会被忽略并且不被包含在序列化和反序列化中
嵌套类(包括内部类)
Gson能够地序列化和反序列化静态嵌套类。但是单纯的内部类不能被Gson序列化和反序列化,因为再反序列的时候需要一个实例的引用去获得实际的类型,你可以通过使用静态内部类或为其提供自定义InstanceCreator来解决此问题,如:
public class A {
public String a;
class B {
public String b;
public B() {
// B的无参构造
}
}
}
注意:上述的类B不能被Gson序列化。
可以通过定义B为静态内部类,来让Gson实现序列化和反序列化,如:
public class A {
public String a;
static class B {
public String b;
public B() {
// B的无参构造
}
}
}
也可以通过一个自定义的creator来解决,如:
public class InstanceCreatorForB implements InstanceCreator {
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"};
// 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 = new Gson();
Collection 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>(){}.getType();
Collection ints2 = gson.fromJson(json, collectionType);
// ==> ints2 is same as ints
这种写法不被推荐,但在JAVA中无法解决这个问题。
集合的限制:
Gson可以序列化任意对象的集合,但不能从中反序列化,因为用户无法指示结果对象的类型。相反,在反序列化时,Collection必须是特定的泛型类型。
序列化和反序列化泛型类型
当你调用toJson(obj),Gson会调用obj.getClass()来获取有关要序列的字段的信息。同样,你还可以传递Myclass.class参数给fromJson(json, MyClass.class)。如果对象是非泛型类型,则此方法正常。但是,如果对象是泛型类型,则由于Java类型擦除而丢失通用类型信息。如:
class Foo {
T value;
}
Gson gson = new Gson();
Foo foo = new Foo();
gson.toJson(foo); // 不能被正确的序列化
gson.fromJson(json, foo.getClass()); // 不能被正确的反序列化
上面的例子代码不能将Bar这个类型正确的反序列化,因为Gson调用list.getClass()去获取class的信息,但返回的是Foo.class。这意味着Gson无法知道这是一个Foo
Type fooType = new TypeToken>() {}.getType();
gson.toJson(foo, fooType);
gson.fromJson(json, fooType);
fooType实际上定义了一个匿名本地内部类,其中包含一个getType()返回完全参数化类型的方法。
任意类型对象集合的序列化和反序列化
有时候,你会处理这样有多种数据类型的JSON数组,如:
['hello',5,{name:'GREETINGS',source:'guest'}]
这相当于一个这样的Collection:
class Event {
private String name;
private String source;
private Event(String name, String source) {
this.name = name;
this.source = source;
}
}
Collection collection = new ArrayList();
collection.add("hello");
collection.add(5);
collection.add(new Event("GREETINGS", "guest"));
你可以直接调用toJson(collection)即可得到上面的JSON数组字符串。但是反序列化fromJson(json, Collection.class)则无法成功,因为Gson不知道如何去匹配数据类型。Gson需要你去提供集合类型的通用版本个体fromJson().这时候你有三个选择:
- 使用Gson的解析器API(低级流解析器或DOM解析器JsonParser)来解析数组元素,然后Gson.fromJson()在每个数组元素上使用。这是首选方法。这是一个演示如何执行此操作的示例。
- 注册一个类型适配器Collection.class,查看每个数组成员并将它们映射到适当的对象。这种方法的缺点是它会影响Gson中其他集合类型的反序列化。
- 注册一个类型的适配器MyCollectionMemberType,并使用fromJson()与Collection
。
仅当数组显示为顶级元素或者您可以更改将集合保持为类型的字段类型时,才可用Collection此方法。
内置的序列化和反序列化
Gson有常用类的内置序列化和反序列化器,如:
JodaTime
自定义序列化和反序列化
有时默认表示不是您想要的。处理一些库类(如DateTime等)时经常会出现这种情况。Gson允许您注册自己的自定义序列化程序和反序列化器。这是通过定义两部分来完成的:
- Json Serializers:必须,为对象定义自定义序列化
- Json Deserializers:必须,为类型定义自定义反序列化
- Instance Creators: 可选,如果无参构造方法或者Json Deserializers被注册了的话,则不需要
例子:
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
private class DateTimeSerializer implements JsonSerializer {
public JsonElement serialize(DateTime src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.toString());
}
}
Gson 在序列化期间遇到DateTime对象时调用serialize()。
实现Deserializers
private class DateTimeDeserializer implements JsonDeserializer {
public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return new DateTime(json.getAsJsonPrimitive().getAsString());
}
}
Gson 在反序列化期间遇到DateTime对象时调用deserialize。
自定义序列化和反序列化实例:
// Custom Serialize
public class DogSerializer implements JsonSerializer {
@Override
public JsonElement serialize(Dog src, Type typeOfSrc, JsonSerializationContext context) {
// This method gets involved whenever the parser encounters the Dog
// object (for which this serializer is registered)
JsonObject object = new JsonObject();
String name = src.getName().replaceAll(" ", "_");
object.addProperty("name", name);
// we create the json object for the dog and send it back to the
// Gson serializer
return object;
}
public static void main(String[] args) {
Animal animal = new Animll();
Dog dog = new Dog("I am a dog");
animal.setAnimal(dog);
// Create the GsonBuilder and register a serializer for the Dog class.
// Whenever the Dog class is encountered Gson calls the DogSerializer
// we set pretty printing own to format the json
Gson gson = new GsonBuilder().registerTypeAdapter(Dog.class, new DogSerializer()).setPrettyPrinting().create();
// Since Animal contains generic type create the type using TypeToken
// class.
Type animalType = new TypeToken>() {
}.getType();
System.out.println(gson.toJson(animal, animalType));
}
}
// The Animal class
public class Animal {
public T animal;
public void setAnimal(T animal) {
this.animal = animal;
}
public T get() {
return animal;
}
}
// The Dog class
public class Dog {
private String name;
public Dog(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
// Custom Deserialize
public class DogDeserialiser implements JsonDeserializer {
@Override
public Dog deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
String name = json.getAsJsonObject().get("name").getAsString();
name = name.replace(" ", "_");
Dog dog = new Dog(name);
return dog;
}
public static void main(String[] args) {
String json = "{\"animal\":{\"name\":\"I am a dog\"}}";
Gson gson = new GsonBuilder().registerTypeAdapter(Dog.class, new DogDeserialiser()).create();
Type animalType = new TypeToken>() {
}.getType();
Animal animal = gson.fromJson(json, animalType);
System.out.println(animal.get().getName());
}
}
编写实例创建器
当进行对象的反序列化时,Gson创建需要一个类的默认实例。一个编写优美的类意味着拥有序列化和反序列化所需要的无参构造方法(可以是public也可以是private)。
通常如果一个类没有无参构造方法就可以使用实例创建器。如:
private class MoneyInstanceCreator implements InstanceCreator {
public Money createInstance(Type type) {
return new Money("1000000", CurrencyCode.USD);
}
}
这里的类型可以是对应的泛型。
有时,您尝试实例化的类型是参数化类型。通常,这不是问题,因为实际的实例是原始类型。这是一个例子:
class MyList extends ArrayList {
}
class MyListInstanceCreator implements InstanceCreator> {
@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 {
private final Class classOfId;
private final long value;
public Id(Class classOfId, long value) {
this.classOfId = classOfId;
this.value = value;
}
}
class IdInstanceCreator implements InstanceCreator> {
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
紧凑型 vs JSON的漂亮输出格式
Gson提供的默认JSON输出是紧凑的JSON格式。这意味着输出JSON结构中不会有任何空格。因此,JSON输出中的字段名称及其值,对象字段和数组内的对象之间不会有空格。同样,输出中将忽略“null”字段。
如果要使用“Pretty Print”功能,则必须Gson使用“ 配置”来配置实例GsonBuilder。Gson没有通过的公共API公开JsonFormatter,所以客户端无法配置默认print settings/margins的JSON输出。目前,我们只提供JsonPrintFormatter默认行长度为80个字符,2个字符缩进和4个字符右边距。
以下是一个示例,说明如何配置Gson实例以使用默认值JsonPrintFormatter而不是JsonCompactFormatter:
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String jsonOutput = gson.toJson(someObject);
Null 对象支持
Gson的默认实现中null值是被忽略的。然而,当你在使用Gson时想为这里字段添加一个默认值时,你可以这样配置Gson,如:
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);
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(versionedObject);
System.out.println(jsonOutput);
System.out.println();
gson = new Gson();
jsonOutput = gson.toJson(versionedObject);
System.out.println(jsonOutput);
输出为:
{"newField":"new","field":"old"}
{"newerField":"newer","newField":"new","field":"old"}
从序列化和反序列化中排除字段
Gson提供大量的排除顶级类,字段和字段类型的机制。下面是灵活的机制,允许字段和类排除。如果以下机制都不能满足您的需求,那么您始终可以使用自定义序列化程序和反序列化程序。
- Java修饰符排除
默认情况下,如果将字段标记为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();
- Gson的@Expose
可以使用@Expose注解去排除你不想被Gson序列化和反序列化的字段。但Gson实例必须通过new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create().
来创建。这样Gson就会排除掉注解注释的字段。 - 如果以上的机制都不适合你,可以编写你自己的排除策略,并加入到Gson中。参考ExclusionStrategy文档。以下为例子:
@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类。如:
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"}
自定义序列化和反序列化共享状态
有时您需要在自定义序列化器/反序列化器之间共享状态)。可以使用以下三种策略来完成此任务:
- 在静态字段中存储共享状态
- 将序列化器/反序列化器声明为父类型的内部类,并使用父类型的实例字段来存储共享状态
- 使用Java ThreadLocal
1和2不是线程安全选项,但3是。
流
除了Gson的对象模型和数据绑定之外,您还可以使用Gson读取和写入流。您还可以组合流和对象模型访问,以获得两种方法中的最佳方法。