1. 概述
本教程將討論如何將 JSON 數組轉換為等效的 <em >java.util.List</em> 對象。Gson 是 Google 開發的 Java 庫,它能夠將 JSON 字符串轉換為 Java 對象,反之亦然。
Gson 類中的該方法 <em >fromJson()</em> 接受兩個參數,第一個參數是 JSON 字符串,第二個參數是 <em >java.lang.reflect.Type</em> 類型。該方法將 JSON 字符串轉換為其第二個參數表示的等效 Java 對象。
我們將創建一個通用的方法,例如 <em >convertJsonArrayToListOfAnyType(String jsonArray, T elementType)</em >,該方法可以將 JSON 數組轉換為List<T>,其中 T 是 <em >List</em> 中元素的類型。
讓我們更深入地瞭解這一點。
2. 問題描述
假設我們有兩個 JSON 數組,一個用於 學生,另一個用於 學校:
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))
);
}我們創建了 TypeToken 對象用於 List<Student> 和 List<School>。 最終,使用 TypeToken 對象,我們獲得了 Type 對象,然後將 JSON 數組轉換為 List<Student> 和 List<School>。
為了促進代碼重用,讓我們嘗試創建一個泛型類,其中包含一個可以接受列表中元素類型的方法,並返回 Type 對象:
class ListWithDynamicTypeElement<T> {
Type getType() {
TypeToken<List<T>> typeToken = new TypeToken<List<T>>(){};
return typeToken.getType();
}
}我們正在實例化一個通用的 TypeToken<List<T>>,用於指定一個元素類型 T。然後我們返回相應的 Type 對象。列表元素類型僅在運行時可用。
讓我們看看該方法的實際應用:
@Test
void givenJsonArray_whenListElementTypeDynamic_thenConvertToJavaListUsingTypeTokenFails() {
Gson gson = new Gson();
assertThrows(IllegalArgumentException.class, () -> gson.fromJson(jsonArrayOfStudents, new ListWithDynamicTypeElement<Student>().getType()));
}雖然該方法編譯成功,但在運行時嘗試創建 studentLst 時,會拋出 IllegalArgumentException 異常。
3. 使用 getParameterized() 在 TypeToken 中的解決方案
在 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> 的 Type 信息。 最終,我們使用 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 定義的目標類型。
這些轉換後的元素被放入一個 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 反射 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() 方法獲取 Type 信息,成功地將 JSON 數組轉換為對應的 List 對象形式。
7. 結論
本文討論了在運行時獲取 <em Type</em> 對象信息四種不同的方法,應用於 <em List<T></em> 對象。最後,我們使用 <em Type</em> 對象通過 Gson 庫中的 <em fromJson()</em> 方法將 JSON 數組轉換為 <em List</em> 對象。
由於最終調用的是 <em fromJson()</em> 方法,因此這些方法的性能非常接近。但是,<em TypeToken.getParamterized()</em> 方法最為簡潔,因此我們推薦使用它。