知識庫 / JSON RSS 訂閱

Gson 與 TypeToken 動態列表項類型支持

JSON
HongKong
6
09:44 PM · Dec 05 ,2025

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&lt;T&gt;</em> 對象。最後,我們使用 <em Type</em> 對象通過 Gson 庫中的 <em fromJson()</em> 方法將 JSON 數組轉換為 <em List</em> 對象。

由於最終調用的是 <em fromJson()</em> 方法,因此這些方法的性能非常接近。但是,<em TypeToken.getParamterized()</em> 方法最為簡潔,因此我們推薦使用它。

發佈 評論

Some HTML is okay.