具有動態清單項目類型的 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 上找到。