好長時間沒寫 Java,發現序列化、反序列化一個 JSON 資料真不是個容易的事情(主要還是年紀大了,記不住)。于是記錄一下使用 Gson 反序列化的方法。文中涉及的代碼都可以在這個 gson-deserialization-example 中找到。
作者本身不懂 Java,本着不負責的态度寫下這些内容,大牛勿噴,想抄代碼的菜鳥請珍重。
本文基于轉換期間沒有異常情況讨論,實際情況請珍重。
初始化
我們先建立一個簡單的 TestMain.java 檔案,用來運作我們後續的測試方法。
TestMain.java1
2
3
4
5public class TestMain{
public static void main(String args[]){
// 運作測試方法
}
}
通過 javac TestMain.java 會生成 TestMain.class 檔案,之後就能通過 java TestMain 運作 class 檔案。
在目前的例子中我們 google 的 Gson( maven 位址) 庫來做序列化和反序列化。簡單起見,我們直接下載下傳 jar 包,放到 lib 目錄下。
因為我們使用了 gson.jar 是以指令會變成這樣:
javac -classpath lib/gson-2.8.0.jar TestMain.java
java -classpath .:lib/gson-2.8.0.jar TestMain
JsonParser
Object
假設我們有這樣一個簡單的 JSON 資料:
1
2
3
4
5
6
7
8
9
10
11
12
{
"name": "Hans Chan",
"age": 18,
"tags": [{
"id": 1,
"text": "JavaScript"
}, {
"id": 2,
"text": "Java"
}]
}
使用 JsonParser 足夠的簡單:
1
2JsonParser jsonParser = new JsonParser();
JsonElement userJsonElement = jsonParser.parse(json);
所有東西都是 抽象 的 JsonElement(api),如果要擷取具體的内容,就得轉換成 JsonObject 或 JsonArray 等類型,擷取方式也非常直覺 .get("key"):
1
2
3
4JsonObject userJsonObject = userJsonElement.getAsJsonObject();
String name = userJsonObject.get("name").getAsString();
int age = userJsonObject.get("age").getAsInt();
JsonArray userTagsJsonArray = userJsonObject.get("tags").getAsJsonArray();
Array
同樣的場景,如果輸入的 json 字元串不是 {} 而是 [],也可以通過上述方法擷取:
1
2
3
4
5
6String json = "[{},{}]"; // 每個 {} 都是一個 Sample JSON
JsonArray userJsonArray = jsonParser.parse(json).getAsJsonArray(); // 不是 getAsJsonObject
for (int i = 0; i < userJsonArray.size(); i++) {
JsonObject userJsonObject = userJsonArray.get(i).getAsJsonObject();
// ...
}
Serialization
JsonElement 的序列化很簡單,直接 .toString() 即可。
1String json = userJsonObject.toString(); // JsonObject
OO
這下子,寫 Java 的哥們就肯定會跳出來說 “這是什麼鬼,一點都不 OO”。的确上面的方式很 js,于是我們就要寫得像 Java 一點,先來兩個 class :
1
2
3
4
5
6
7
8
9
10
11private class Tag{
private int id;
private String text;
// 此處省略 Getter and Setter
}
private class User{
private String name;
private int age;
private List tags;
// 此處省略 Getter and Setter
}
Object
大家注意了,我要變形了!(敲黑闆)
1
2Gson gson = new Gson();
User user = gson.fromJson(json, User.class);
通過 Gson,String 被轉換成指定的 User.class,然後我們就可以愉快地操作這個執行個體了:
1
2
3
4
5List tags = user.getTags();
for (int i = 0; i < tags.size(); i++) {
Tag tag = tags.get(i);
System.out.println("tag " + tag.getId() + ": " + tag.getText());
}
Array
還是同樣的例子,如果是 [] 怎麼辦?我們當然期望是獲得一個 List 啦,但沒有 List.class 這個東西,怎麼破?沒關系,Gson 裡面還有個 TypeToken 是可以跟你幹這事的,我們隻需要這樣:
1
2
3
4
5
6// import com.google.common.reflect.TypeToken;
TypeToken typeToken = new TypeToken>() {};
// import java.lang.reflect.Type;
Type type = typeToken.getType();
List users = gson.fromJson(json, type);
還是可以愉快地玩耍的,不是麽 😂
Serialization
Class 要反序列化就還是要依賴回 Gson 提供的 toJson 方法:
1String json = gson.toJson(user); // User
GsonBuilder
很多時候,輸入的 json 總有那麼一點不盡人意,例如下面這個例子:
1
2
3
4
5
6
7
8
9
10
11
12{
"id": 3,
"name": "Hans Chan",
"registrationTime": "1999-09-19 18:10:22",
"data": {
"some": "complex data",
"we": {
"do-NOT": ["care", "about", "what", "inside"],
"BUT": "needed!"
}
}
}
id 是我們需要的資料,但序列化出去的時候不想顯示
registrationTime 可能不是一個我們想要的格式
data 可能是我們不是很關心結構,但又需要儲存裡面的内容
利用 GsonBuilder 和 Annotation 我們就可以實作上面兩個功能:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28private class BaseUser{
// import com.google.gson.annotations.Expose;
@Expose(serialize = false, deserialize = true)
private int id;
@Expose
private String name;
// import com.google.gson.annotations.SerializedName;
@SerializedName("registrationTime")
@Expose
private Date registration;
public int getId(){
return id;
}
public Date getRegistration(){
return registration;
}
}
private class CustomBUser extends BaseUser{
@Expose
private JsonElement data;
public JsonElement getData(){
return data;
}
}
registrationTime 的格式我們用 GsonBuilder 聲明:
1
2
3
4
5
6Gson deserializationGson = new GsonBuilder()
// 不導出實體中沒有用 @Expose 注解的屬性
.excludeFieldsWithoutExposeAnnotation()
// 時間格式
.setDateFormat("yyyy-MM-dd HH:mm:ss")
.create();
愉快地玩耍吧:
1
2
3CustomBUser cbu = deserializationGson.fromJson(json, CustomBUser.class);
System.out.println("id: " + cbu.getId());
System.out.println(cau.getData());
自定義序列化和反序列化
上面的例子中,data 是直接用一個 JsonElement 來處理的,如果有更加個性化的要求,那就需要自己寫序列化和反序列化方法了。這裡我們自己實作一個 CustomUserData 類,用來處理 data 資料,直接實作上面相同的功能:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34private static class CustomUserData{
private JsonElement ctx;
public CustomUserData(JsonElement ctx){
this.ctx = ctx;
}
public String toString(){
return this.ctx.toString();
}
}
private class CustomAUser extends BaseUser{
@Expose
private CustomUserData data;
public CustomUserData getData(){
return data;
}
}
// 自定義反序列化方法
private static class CustomUserDataDeserializeAdapter implements JsonDeserializer{
@Override
public CustomUserData deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException{
// 這裡實作複雜的功能
return new CustomUserData(json);
}
}
// 自定義序列化方法
public static class CustomUserDataSerializeAdapter implements JsonSerializer{
@Override
public JsonElement serialize(CustomUserData src, Type typeOfSrc, JsonSerializationContext context){
// 這裡實作複雜的功能
return src.ctx;
}
}
通過 registerTypeAdapter 給 GsonBuilder 注冊上面的自定義序列化方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18Gson deserializationGson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.setDateFormat("yyyy-MM-dd HH:mm:ss")
.registerTypeAdapter(CustomUserData.class, new CustomUserDataDeserializeAdapter())
.create();
Gson serializationGson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.setDateFormat("yyyy/MM/dd HH:mm:ss")
.registerTypeAdapter(CustomUserData.class, new CustomUserDataSerializeAdapter())
.setPrettyPrinting()
.create();
System.out.println("---------- CustomAUser ----------");
CustomAUser cau = deserializationGson.fromJson(json, CustomAUser.class);
System.out.println("id: " + cau.getId());
// System.out.println(cau.getRegistration());
// System.out.println(cau.getData()); // .toString()
System.out.println(serializationGson.toJson(cau));