具有動態清單項目類型的 Gson TypeToken
1. 概述
在本教程中,我們將討論將 JSON 陣列轉換為等效的java.util.List物件。 Gson 是 Google 的 Java 函式庫,可協助將 JSON 字串轉換為 Java 對象,反之亦然。
該函式庫中的Gson類別具有fromJson()方法,該方法接受兩個參數,第一個參數是 JSON String ,第二個參數是java.lang.reflect.Type類型。此方法將 JSON String轉換為由其第二個參數表示的類型的等效 Java 物件。
我們將提出一個通用方法,例如convertJsonArrayToListOfAnyType(String jsonArray, T elementType),它可以將JSON數組轉換為List<T> ,其中T是List中元素的類型。
讓我們更多地了解這一點。
2. 問題描述
假設我們有兩個 JSON 數組,一個用於Student ,一個用於School :
final String jsonArrayOfStudents =
"["
+ "{\"name\":\"John\", \"grade\":\"1\"}, "
+ "{\"name\":\"Tom\", \"grade\":\"2\"}, "
+ "{\"name\":\"Ram\", \"grade\":\"3\"}, "
+ "{\"name\":\"Sara\", \"grade\":\"1\"}"
+ "]";
final String jsonArrayOfSchools =
"["
+ "{\"name\":\"St. John\", \"city\":\"Chicago City\"}, "
+ "{\"name\":\"St. Tom\", \"city\":\"New York City\"}, "
+ "{\"name\":\"St. Ram\", \"city\":\"Mumbai\"}, "
+ "{\"name\":\"St. Sara\", \"city\":\"Budapest\"}"
+ "]";
通常,我們可以使用Gson類別將陣列轉換為List物件:
@Test
void givenJsonArray_whenListElementTypeDynamic_thenConvertToJavaListUsingTypeToken() {
Gson gson = new Gson();
TypeToken<List<Student>> typeTokenForListOfStudents = new TypeToken<List<Student>>(){};
TypeToken<List<School>> typeTokenForListOfSchools = new TypeToken<List<School>>(){};
List<Student> studentsLst = gson.fromJson(jsonArrayOfStudents, typeTokenForListOfStudents.getType());
List<School> schoolLst = gson.fromJson(jsonArrayOfSchools, typeTokenForListOfSchools.getType());
assertAll(
() -> studentsLst.forEach(e -> assertTrue(e instanceof Student)),
() -> schoolLst.forEach(e -> assertTrue(e instanceof School))
);
}
我們為List<Student>和List<School>建立了TypeToken物件。最後,使用TypeToken對象,我們取得了Type對象,然後將 JSON 陣列轉換為List<Student>和List<School> 。
為了促進重用,讓我們嘗試建立一個泛型類,其中的方法可以取得List中的元素類型並傳回Type物件:
class ListWithDynamicTypeElement<T> {
Type getType() {
TypeToken<List<T>> typeToken = new TypeToken<List<T>>(){};
return typeToken.getType();
}
}
我們正在為給定元素類型T實例化通用TypeToken<List<T>> 。然後我們傳回對應的Type物件。列表元素類型僅在運行時可用。
讓我們看看該方法的實際效果:
@Test
void givenJsonArray_whenListElementTypeDynamic_thenConvertToJavaListUsingTypeTokenFails() {
Gson gson = new Gson();
List<Student> studentsLst = gson.fromJson(jsonArrayOfStudents, new ListWithDynamicTypeElement<Student>().getType());
assertFalse(studentsLst.get(0) instanceof Student);
assertThrows(ClassCastException.class, () -> studentsLst.forEach(e -> assertTrue(e instanceof Student)));
}
當該方法編譯時,它會在運行時失敗,並在我們嘗試迭代studentLst時引發ClassCastException 。此外,我們看到清單中的元素不是Student類型。
3.在TypeToken中使用getParameterized()解決方案
在 Gson 2.10 版本中, getParamterized() **TypeToken** .這使得開發人員能夠處理在編譯期間列表元素的類型資訊不可用的情況。
讓我們看看這個新方法如何幫助傳回參數化類別的Type資訊:
Type getGenericTypeForListFromTypeTokenUsingGetParameterized(Class elementClass) {
return TypeToken.getParameterized(List.class, elementClass).getType();
}
getParamterized()方法在執行時呼叫時,將傳回List物件的實際類型及其元素類型。此外,這將有助於Gson類別將 JSON 陣列轉換為具有其元素的正確類型資訊的精確List物件。
讓我們看看該方法的實際效果:
@Test
void givenJsonArray_whenListElementTypeDynamic_thenConvertToJavaListUsingGetParameterized() {
Gson gson = new Gson();
List<Student> studentsLst = gson.fromJson(jsonArrayOfStudents, getGenericTypeForListFromTypeTokenUsingGetParameterized(Student.class));
List<School> schoolLst = gson.fromJson(jsonArrayOfSchools, getGenericTypeForListFromTypeTokenUsingGetParameterized(School.class));
assertAll(
() -> studentsLst.forEach(e -> assertTrue(e instanceof Student)),
() -> schoolLst.forEach(e -> assertTrue(e instanceof School))
);
}
我們使用getGenericTypeForListFromTypeTokenUsingGetParameterized()方法來取得List<Student>和List<School>.最後,使用fromJson()方法,我們成功地將 JSON 陣列轉換為它們各自的 Java List物件。
4. 使用JsonArray的解決方案
Gson 函式庫有一個JsonArray類別來表示 JSON 陣列。我們將使用它將 JSON 陣列轉換為List物件:
<T> List<T> createListFromJsonArray(String jsonArray, Type elementType) {
Gson gson = new Gson();
List<T> list = new ArrayList<>();
JsonArray array = gson.fromJson(jsonArray, JsonArray.class);
for(JsonElement element : array) {
T item = gson.fromJson(element, elementType);
list.add(item);
}
return list;
}
首先,我們使用Gson類別中常用的fromJson()方法將 JSON 陣列字串轉換為JsonArray物件。然後,我們將JsonArray物件中的每個JsonElement元素轉換為createListFromJsonArray()方法中第二個參數elementType定義的目標Type 。
每個轉換後的元素都會放入一個List中,最後回傳。
現在,讓我們看看該方法的實際效果:
@Test
void givenJsonArray_whenListElementTypeDynamic_thenConvertToJavaListUsingJsonArray() {
List<Student> studentsLst = createListFromJsonArray(jsonArrayOfStudents, Student.class);
List<School> schoolLst = createListFromJsonArray(jsonArrayOfSchools, School.class);
assertAll(
() -> studentsLst.forEach(e -> assertTrue(e instanceof Student)),
() -> schoolLst.forEach(e -> assertTrue(e instanceof School))
);
}
我們使用createListFromJsonArray()方法成功地將學生和學校的 JSON 陣列轉換為List<Student>和List<School> 。
5. 使用 Guava 的TypeToken的解決方案
與 Gson 庫中的TypeToken類別類似, Guava 中的TypeToken類別也可以在運行時捕獲泛型類型。否則,由於 Java 中的類型擦除,這是不可能的。
讓我們來看看使用 Guava TypeToken類別的實作:
<T> Type getTypeForListUsingTypeTokenFromGuava(Class<T> type) {
return new com.google.common.reflect.TypeToken<List<T>>() {}
.where(new TypeParameter<T>() {}, type)
.getType();
}
方法where()透過將類型參數替換為變數type中的類別來傳回TypeToken物件。
最後,我們可以看看它的實際效果:
@Test
void givenJsonArray_whenListElementTypeDynamic_thenConvertToJavaListUsingTypeTokenFromGuava() {
Gson gson = new Gson();
List<Student> studentsLst = gson.fromJson(jsonArrayOfStudents, getTypeForListUsingTypeTokenFromGuava(Student.class));
List<School> schoolLst = gson.fromJson(jsonArrayOfSchools, getTypeForListUsingTypeTokenFromGuava(School.class));
assertAll(
() -> studentsLst.forEach(e -> assertTrue(e instanceof Student)),
() -> schoolLst.forEach(e -> assertTrue(e instanceof School))
);
}
我們再次使用fromJson()方法將 JSON 陣列轉換為對應的List物件。但是,我們在 Guava 函式庫的幫助下透過getTypeForListUsingTypeTokenFromGuava()方法獲得了Type物件。
6. 使用Java Reflection API的ParameterizedType的解決方案
ParameterizedType是一個接口,是 Java 反射 API 的一部分。它有助於表示參數化類型,例如Collection<String> 。
讓我們實作ParamterizedType來表示具有參數化類型的任何類別:
public class ParameterizedTypeImpl implements ParameterizedType {
private final Class<?> rawType;
private final Type[] actualTypeArguments;
private ParameterizedTypeImpl(Class<?> rawType, Type[] actualTypeArguments) {
this.rawType = rawType;
this.actualTypeArguments = actualTypeArguments;
}
public static ParameterizedType make(Class<?> rawType, Type ... actualTypeArguments) {
return new ParameterizedTypeImpl(rawType, actualTypeArguments);
}
@Override
public Type[] getActualTypeArguments() {
return actualTypeArguments;
}
@Override
public Type getRawType() {
return rawType;
}
@Override
public Type getOwnerType() {
return null;
}
}
變數actualTypeArguments將儲存泛型類別中類型參數的類別信息,而rawType表示泛型類別。 make()方法傳回參數化類別的Type物件。
最後,讓我們看看它的實際效果:
@Test
void givenJsonArray_whenListElementTypeDynamic_thenConvertToJavaListUsingParameterizedType() {
Gson gson = new Gson();
List<Student> studentsLst = gson.fromJson(jsonArrayOfStudents, ParameterizedTypeImpl.make(List.class, Student.class));
List<School> schoolLst = gson.fromJson(jsonArrayOfSchools, ParameterizedTypeImpl.make(List.class, School.class));
assertAll(
() -> studentsLst.forEach(e -> assertTrue(e instanceof Student)),
() -> schoolLst.forEach(e -> assertTrue(e instanceof School))
);
}
我們使用make()方法來取得參數化List類別的Type訊息,並成功將 JSON 陣列轉換為其各自的List物件形式。
七、結論
在本文中,我們討論了在運行時獲取List<T>物件的Type資訊的四種不同方法。最後,我們使用 Gson 函式庫中的fromJson()方法使用Type物件將 JSON 陣列轉換為List物件。
由於最終調用的是 fromJson() 方法,因此所有這些方法的效能都非常接近。但是, TypeToken.getParamterized()方法是最簡潔的,因此我們建議使用它。
與往常一樣,所使用的程式碼可以在 GitHub 上找到。