Gson是一個Java庫,用來實現Json和Java對象之間的相互轉換。Gson是一個託管在https://github.com/google/gson的開源項目。
Gson中主要的類是Gson,也可以使用類GsonBuilder在建立Gson對象的同時設定一些選項。
Gson對象在處理Json時不會儲存任何狀態,所以使用者能夠很輕鬆的對同一個Gson對象進行多次序列化、還原序列化等操作。
樣本:基本使用
//SerializationGson gson = new Gson();gson.toJson(1); //==> prints 1gson.toJson("abcd"); //==> prints "abcd"gson.toJson(new Long(10)); //==> prints 10int[] values = { 1 };gson.toJson(values); //==> prints [1]//Deserializationint one = gson.fromJson("1", int.class);Integer one = gson.fromJson("1", Integer.class);Long one = gson.fromJson("1", Long.class);Boolean f = gson.fromJson("false", Boolean.class);String str = gson.fromJson("\"abc\"", String.class);String anotherStr = gson.fromJson("[\"abc\"]", String.class);//SerializationBagOfPrimitives obj = new BagOfPrimitives();Gson gson = new Gson();String json = gson.toJson(obj); //==> json is {"value1":1,"value2":"abc"}
樣本:對象與Json之間轉換
定義BagOfPrimitives類:
class BagOfPrimitives { private int value1 = 1; private String value2 = "abc"; private transient int value3 = 3; BagOfPrimitives() { // no-args constructor }}
序列化為Json:
//SerializationBagOfPrimitives obj = new BagOfPrimitives();Gson gson = new Gson();String json = gson.toJson(obj); //==> json is {"value1":1,"value2":"abc"}
不要序列化含有循環參考的對象,否則會造成無限的遞迴。
還原序列化:
//DeserializationBagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class); //==> obj2 is just like obj
處理對象時的一些細節:
- 推薦使用私人欄位(譯者:可以通過反射擷取私人欄位的名稱和值)
- 沒有必要使用標註指明哪些欄位該被序列化或者還原序列化。在當前類(也包括其父類)中的所有欄位都預設會被序列化/還原序列化。
- 如果某欄位在聲明時使用了關鍵字transient,預設情況下不會被序列化/還原序列化。
- Gson如下處理null欄位:
- 序列化時候null欄位會被跳過
- 還原序列化時,類中有但Json中沒有的欄位將設值為null。
- synthetic欄位不會被序列化/還原序列化。
- 在外部類(outer classes)中的內部類(inner classes)、匿名類(anonymous classes)和局部類(local classes)中的欄位不會被序列化/還原序列化。
嵌套類(包括內部類)的處理
Gson可以很輕鬆地序列化嵌套類,且能夠還原序列化靜態嵌套類。Gson無法自動地還原序列化純粹的內部類,是因為內部類的無參建構函式需要引用包含它的對象(即外部類的執行個體)。要還原序列化靜態類,可以將內部類靜態化或者提供一個自訂的執行個體創造器(instance creator)。下面是一個樣本:
public class A { public String a; class B { public String b; public B() { // No args constructor for B } }}
上面的類B無法被Gson序列化。由於類B是一個(非靜態)內部類,Gson也無法還原序列化{"b":"abc"}到類B的執行個體中。如果B被聲明為static class 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"};//Serializationgson.toJson(ints); ==> prints [1,2,3,4,5]gson.toJson(strings); ==> prints ["abc", "def", "ghi"]//Deserializationint[] ints2 = gson.fromJson("[1,2,3,4,5]", int[].class);==> ints2 will be same as ints
Gson也支援具有複雜資料類型的多維陣列。
樣本:集合(Collection)
Gson gson = new Gson();Collection<Integer> ints = Lists.immutableList(1,2,3,4,5);//SerializationString json = gson.toJson(ints); //==> json is [1,2,3,4,5]//DeserializationType collectionType = new TypeToken<Collection<Integer>>(){}.getType();Collection<Integer> ints2 = gson.fromJson(json, collectionType);//ints2 is same as ints
處理集合(Collection)時的限制:
- 可以序列化任意對象的集合,還原序列化就不行了。
- 還原序列化時,集合必須是指定的泛型。
序列化/還原序列化泛型
當使用toJson(obj)時,Gson調用obj.getClass()擷取欄位資訊以在序列化中使用。類似的,也可以將對象MyClass.class作為參數傳遞給fromJson(json, MyClass.class)方法,這可以在在對象不是泛型的時候使用。不過,當對象是一個泛型型別的對象,由於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 correctlygson.fromJson(json, foo.getClass()); // Fails to deserialize foo.value as Bar
上面的代碼將value解釋為Bar類型,這是因為Gson調用foo.getClass()擷取類的資訊,但是這種那個方法返回的是一個原始的類,即Foo.class。這意味著Gson無法知道這是一個Foo<Bar>類型的對象。
要解決這個問題,可以是為你的泛型指定正確的參數化類別型。可以使用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 = 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無法將json中的的內容與類型對應起來。Gson需要你在fromJson中提供一個通用版本的集合類型。你有三個選擇:
方案1:使用Gson解析器的API(低級的流解析器或者DOM解析器JsonParser)去解析數組元素,然後使用Gson.fromJson()處理每一個數組元素。這是首選的方案。
方案2:為Collection.class註冊一類型適配器將數組中的元素映射到合適的對象。這種方法的缺點是會使你在處理其他的集合類型時候產生不便。
方案3:為MyCollectionMemberType註冊一個類型適配器,在fromJson中使用Collection<MyCollectionMemberType>。只有當數組看起來像一個進階的元素或者你能夠將欄位類型改成Collection<MyCollectionMemberType>,這種方法才比較可行。
內建的序列化/還原序列化器
Gson為常用的但是預設表示可能並不合適的類提供了序列化/還原序列化器。
下面是這些類的一個列表:
- java.net.URL,例如會序列化為字串 http://code.google.com/p/google-gson/
- java.net.URI,例如會序列化為字串/p/google-gson/
自訂序列化/還原序列化
有時候,Gson的預設實現並不是你想要的,這在處理一些類庫時(例如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會檢查類型適配器是否實現了多個介面,並為這些介面註冊類型適配器。
寫一個序列化器
下面是一個為DateTime自訂序列化器的樣本:
private class DateTimeSerializer implements JsonSerializer<DateTime> { public JsonElement serialize(DateTime src, Type typeOfSrc, JsonSerializationContext context) { return new JsonPrimitive(src.toString()); }}
Gson在序列化DateTime執行個體時會調用toJson()。
寫一個還原序列化器
下面的樣本是講如何寫一個DateTime類的還原序列化器:
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對象時,會調用 fromJson()。
對於序列化器/還原序列化器,應注意:
- 很多情況下,你想要註冊一個處理常式將泛型與一個原始類型對應起來,
- 例如,假如你有一個叫做Id的類來表示和轉換Id
- Id<T>類型中,所有的泛型擁有相同的序列化器,這個序列化器就是輸出id的值
- 還原序列化器很像,但並不會完全一樣。例如要返回一個Id<T>對象,需要調用new Id(Class<T>, String)。
- Gson支援註冊一個處理常式,你也可以為指定的泛型註冊指定的處理常式。toJson和fromJson的Type參數包含了泛型資訊以協助你寫出一個可以將所有的泛型與同一個原始類型對應起來的處理常式。
寫一個執行個體建立器
在還原序列化一個對象時,Gson需要建立一個類的執行個體。在序列化/還原序列化時具有良好表現的類是指這個類擁有一個無參建構函式。通常,當處理一個類庫中沒有無參建構函式的類時,需要使用執行個體建立器。
執行個體建立器樣本:
private class MoneyInstanceCreator implements InstanceCreator<Money> { public Money createInstance(Type type) { return new Money("1000000", CurrencyCode.USD); }}
參數化型別的執行個體建立器
有時候要執行個體化的類型會是一個參數化型別。總的來說,由於真正的執行個體是一個原始類型,所以這不是什麼問題。下面是一個樣本:
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);
Null 物件
在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);
輸出:
版本支援
可以使用@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支援使用很多方法來去除類、欄位、欄位類型。如果下面的方法無法滿足你的需求,可以使用自訂序列化/還原序列化器的方法。
1.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();
2.使用@Expose欄位排除
這個特性允許你在類中標記特定的欄位使其在序列化/還原序列化中不被排除/被排除。要使用這個標註,你應該使用new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()建立Gson。Gson執行個體會排除類中所有沒被@Expose標註的欄位。
3.使用者定義排除策略
如果上面的排除方法無法滿足需求,你也可以自訂自己的排除策略。更多內容,可以參考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); }
輸出:
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解析null出錯解決辦法
GSON有一個缺點就是無法設定null替換,
我們只能手動的批量替換伺服器返回的null了,正常的介面定義的時候是絕對不允許伺服器返回null的,後台結果卻總會出現null!
如果搜尋的話有一個常見的答案,
Gson gson = new GsonBuilder().serializeNulls().create();
但是這個卻無法解決反序列問題,怎麼解決呢?
解決辦法如下:
Gson gson = new GsonBuilder().registerTypeAdapterFactory(new NullStringToEmptyAdapterFactory()).create();//然後用上面一行寫的gson來序列化和還原序列化實體類typegson.fromJson(json, type);gson.toJson(type);//NullStringToEmptyAdapterFactory的代碼public class NullStringToEmptyAdapterFactory<T> implements TypeAdapterFactory { @SuppressWarnings("unchecked") public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { Class<T> rawType = (Class<T>) type.getRawType(); if (rawType != String.class) { return null; } return (TypeAdapter<T>) new StringNullAdapter(); }}// StringNullAdapter代碼public class StringNullAdapter extends TypeAdapter<String> { @Override public String read(JsonReader reader) throws IOException { // TODO Auto-generated method stub if (reader.peek() == JsonToken.NULL) { reader.nextNull(); return ""; } return reader.nextString(); } @Override public void write(JsonWriter writer, String value) throws IOException { // TODO Auto-generated method stub if (value == null) { writer.nullValue(); return; } writer.value(value); }}