知識庫 / JSON / Jackson RSS 訂閱

Jackson: java.util.LinkedHashMap 無法轉換為 X

Jackson
HongKong
8
09:51 PM · Dec 05 ,2025

1. 概述

Jackson 是一個廣泛使用的 Java 庫,它允許我們方便地序列化/反序列化 JSON 或 XML 數據。

有時,當我們嘗試將 JSON 或 XML 反序列化為對象集合時,可能會遇到 `java.lang.ClassCastException: java.util.LinkedHashMap 無法轉換為 X 異常。

在本教程中,我們將討論這種異常可能發生的原因以及如何解決該問題。

2. 理解問題

讓我們創建一個簡單的 Java 應用程序,以重現此異常並瞭解異常何時發生。

2.1. 創建POJO類

讓我們從一個簡單的POJO類開始:

public class Book {
    private Integer bookId;
    private String title;
    private String author;
    //getters, setters, constructors, equals and hashcode omitted
}

假設我們有一個名為 books.json 的文件,該文件包含一個 JSON 數組,其中包含三個圖書:

[ {
    "bookId" : 1,
    "title" : "A Song of Ice and Fire",
    "author" : "George R. R. Martin"
}, {
    "bookId" : 2,
    "title" : "The Hitchhiker's Guide to the Galaxy",
    "author" : "Douglas Adams"
}, {
    "bookId" : 3,
    "title" : "Hackers And Painters",
    "author" : "Paul Graham"
} ]

接下來,我們將查看當我們嘗試將我們的 JSON 示例反序列化為 List<Book> 時會發生什麼。

2.2. 將 JSON 反序列化為 List<Book>

讓我們看看如何通過將此 JSON 文件反序列化為 List<Book> 對象,並從中讀取元素來重現該類類型轉換問題:

@Test
void givenJsonString_whenDeserializingToList_thenThrowingClassCastException() 
  throws JsonProcessingException {
    String jsonString = readFile("/to-java-collection/books.json");
    List<Book> bookList = objectMapper.readValue(jsonString, ArrayList.class);
    assertThat(bookList).size().isEqualTo(3);
    assertThatExceptionOfType(ClassCastException.class)
      .isThrownBy(() -> bookList.get(0).getBookId())
      .withMessageMatching(".*java.util.LinkedHashMap cannot be cast to .*com.baeldung.jackson.tocollection.Book.*");
}

我們使用了 AssertJ 庫來驗證當調用 bookList.get(0).getBookId() 時,是否會拋出預期的異常,並且其消息與我們問題陳述中記錄的消息相符。

測試通過,這意味着我們成功地重現了問題。

2.3. 異常拋出的原因

如果我們仔細查看異常消息 class java.util.LinkedHashMap cannot be cast to class … Book, 可能會產生一些問題。

我們聲明瞭變量 bookList 為類型 List<Book>, 但 Jackson 嘗試將 LinkedHashMap 類型轉換為我們的 Book 類的原因是什麼? 此外,LinkedHashMap 從何而來?

首先,我們確實聲明瞭 bookList 為類型 List<Book>。 但是,當我們調用 objectMapper.readValue() 方法時,我們傳入了 ArrayList.class 作為<em>Class</em> 對象。因此,Jackson 會將 JSON 內容反序列化為<em>ArrayList</em> 對象,但它不知道<em>ArrayList</em> 對象中應該包含什麼類型的元素。</strong>

其次,當 Jackson 嘗試在 JSON 中反序列化對象但未提供目標類型信息時,它將使用默認類型:<em>LinkedHashMap</em>。</strong> 換句話説,在反序列化之後,我們將會得到一個<em>ArrayList</em> 對象,其中包含<em>LinkedHashMap</em> 對象。 在<em>Map</em> 中,鍵是屬性的名稱,例如 “bookId”, “title”  以及其他屬性,而值是對應屬性的值:

現在我們已經理解了問題的根源,讓我們討論如何解決它。

3. 將 TypeReference 傳遞給 objectMapper.readValue()

為了解決這個問題,我們需要讓 Jackson 知道元素的類型。但是,編譯器不允許我們做諸如 objectMapper.readValue(jsonString, ArrayList<Book>.class) 這樣的操作。

相反,我們可以將 TypeReference 對象傳遞給 objectMapper.readValue(String content, TypeReference valueTypeRef) 方法。

在這種情況下,我們只需要將 new TypeReference<List<Book>>() {} 作為第二個參數傳遞:

@Test
void givenJsonString_whenDeserializingWithTypeReference_thenGetExpectedList() 
  throws JsonProcessingException {
    String jsonString = readFile("/to-java-collection/books.json");
    List<Book> bookList = objectMapper.readValue(jsonString, new TypeReference<List<Book>>() {});
    assertThat(bookList.get(0)).isInstanceOf(Book.class);
    assertThat(bookList).isEqualTo(expectedBookList);
}

如果運行測試,它將通過。因此,通過一個 TypeReference 對象解決了我們的問題。

4. 將 JavaType 傳遞給 objectMapper.readValue()

在上一節中,我們討論了將 Class 對象或 TypeReference 對象作為第二個參數傳遞給 objectMapper.readValue() 方法。

objectMapper.readValue() 方法仍然接受 JavaType 對象作為第二個參數。 JavaType 是類型令牌類(type-token classes)的基礎類。它將被反序列化器使用,以便在反序列化過程中,反序列化器知道目標類型。

我們可以通過 TypeFactory 實例構建 JavaType 對象,並且可以從 objectMapper.getTypeFactory() 中檢索 TypeFactory 對象。

讓我們回到我們的示例書本示例。 在這個示例中,我們想要的目標類型是 ArrayList&lt;Book&gt;

因此,我們可以使用這個要求構建一個 CollectionType

objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, Book.class);

現在我們來編寫一個單元測試,以驗證將 JavaType 傳遞給 readValue() 方法是否能解決我們的問題:

@Test
void givenJsonString_whenDeserializingWithJavaType_thenGetExpectedList() 
  throws JsonProcessingException {
    String jsonString = readFile("/to-java-collection/books.json");
    CollectionType listType = 
      objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, Book.class);
    List<Book> bookList = objectMapper.readValue(jsonString, listType);
    assertThat(bookList.get(0)).isInstanceOf(Book.class);
    assertThat(bookList).isEqualTo(expectedBookList);
}

測試通過的前提是運行它。因此,這個問題也可以通過這種方式解決。

5. 使用 JsonNode 對象和 objectMapper.convertValue() 方法

我們已經看到了將 TypeReferenceJavaType 對象傳遞給 objectMapper.readValue() 方法的解決方案。

或者,我們可以使用 Jackson 中的樹模型節點,然後通過調用 JsonNode 對象來將其轉換為所需類型,這需要調用 objectMapper.convertValue() 方法。

同樣,我們也可以將 TypeReferenceJavaType 對象傳遞給 objectMapper.convertValue() 方法。

讓我們看看每種方法在實踐中的效果。

首先,讓我們創建一個測試方法,使用 TypeReference 對象和 objectMapper.convertValue() 方法:

@Test
void givenJsonString_whenDeserializingWithConvertValueAndTypeReference_thenGetExpectedList() 
  throws JsonProcessingException {
    String jsonString = readFile("/to-java-collection/books.json");
    JsonNode jsonNode = objectMapper.readTree(jsonString);
    List<Book> bookList = objectMapper.convertValue(jsonNode, new TypeReference<List<Book>>() {});
    assertThat(bookList.get(0)).isInstanceOf(Book.class);
    assertThat(bookList).isEqualTo(expectedBookList);
}

現在讓我們看看當我們將一個 JavaType 對象傳遞到 objectMapper.convertValue() 方法時會發生什麼:

@Test
void givenJsonString_whenDeserializingWithConvertValueAndJavaType_thenGetExpectedList() 
  throws JsonProcessingException {
    String jsonString = readFile("/to-java-collection/books.json");
    JsonNode jsonNode = objectMapper.readTree(jsonString);
    List<Book> bookList = objectMapper.convertValue(jsonNode, 
      objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, Book.class));
    assertThat(bookList.get(0)).isInstanceOf(Book.class);
    assertThat(bookList).isEqualTo(expectedBookList);
}

如果運行這兩個測試用例,它們都會通過。因此,使用 objectMapper.convertValue() 方法是解決該問題的另一種方式。

6. 創建通用的反序列化方法

我們已經討論瞭如何解決在將 JSON 數組反序列化為 Java 集合時遇到的類類型轉換問題。在實際應用中,我們可能需要創建一個通用的方法來處理不同元素類型。

這對於我們來説並不難。

我們可以通過調用 objectMapper.readValue() 方法時傳遞一個 JavaType 對象:

public static <T> List<T> jsonArrayToList(String json, Class<T> elementClass) throws IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    CollectionType listType = 
      objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, elementClass);
    return objectMapper.readValue(json, listType);
}

接下來,讓我們創建一個單元測試方法來驗證其是否按預期工作:

@Test
void givenJsonString_whenCalljsonArrayToList_thenGetExpectedList() throws IOException {
    String jsonString = readFile("/to-java-collection/books.json");
    List<Book> bookList = JsonToCollectionUtil.jsonArrayToList(jsonString, Book.class);
    assertThat(bookList.get(0)).isInstanceOf(Book.class);
    assertThat(bookList).isEqualTo(expectedBookList);
}

如果運行測試,則測試會通過。

為什麼不使用 TypeReference 方式構建泛型方法,因為它看起來更緊湊?

讓我們創建一個泛型實用方法,並將相應的 TypeReference 對象傳遞給 objectMapper.readValue() 方法:

public static <T> List<T> jsonArrayToList(String json, Class<T> elementClass) throws IOException {
    return new ObjectMapper().readValue(json, new TypeReference<List<T>>() {});
}

該方法看起來很直接。

如果我們再次運行該測試方法,我們將得到以下結果:

java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.baeldung...Book ...

哎呀,發生異常了!

我們傳遞了一個 TypeReference 對象到 readValue() 方法中,並且我們之前已經知道這種方式可以解決類類型轉換問題。所以,為什麼在這種情況下我們仍然看到相同的異常

這是因為我們的方法是通用的。 類型參數 T 在運行時無法被重體現,即使我們傳遞了一個帶有類型參數 TTypeReference 實例。

7. 使用 Jackson 進行 XML 反序列化

除了 JSON 序列化/反序列化,Jackson 庫還可以用於序列化/反序列化 XML。

讓我們做一個簡單的例子,以檢查在將 XML 反序列化為 Java 集合時是否會遇到相同的問題。

首先,讓我們創建一個 XML 文件 books.xml

<ArrayList>
    <item>
        <bookId>1</bookId>
        <title>A Song of Ice and Fire</title>
        <author>George R. R. Martin</author>
    </item>
    <item>
        <bookId>2</bookId>
        <title>The Hitchhiker's Guide to the Galaxy</title>
        <author>Douglas Adams</author>
    </item>
    <item>
        <bookId>3</bookId>
        <title>Hackers And Painters</title>
        <author>Paul Graham</author>
    </item>
</ArrayList>

接下來,就像我們為 JSON 文件所做的那樣,我們創建一個單元測試方法,以驗證類轉換異常是否會被拋出:

@Test
void givenXml_whenDeserializingToList_thenThrowingClassCastException() 
  throws JsonProcessingException {
    String xml = readFile("/to-java-collection/books.xml");
    List<Book> bookList = xmlMapper.readValue(xml, ArrayList.class);
    assertThat(bookList).size().isEqualTo(3);
    assertThatExceptionOfType(ClassCastException.class)
      .isThrownBy(() -> bookList.get(0).getBookId())
      .withMessageMatching(".*java.util.LinkedHashMap cannot be cast to .*com.baeldung.jackson.tocollection.Book.*");
}

我們的測試將通過,如果我們將它運行一次。換句話説,與 XML 反序列化一樣,相同的問題也出現在那裏。

但是,如果我們知道如何解決 JSON 反序列化問題,那麼在 XML 反序列化中解決它就非常簡單。

由於 XmlMapperObjectMapper 的子類,我們針對 JSON 反序列化的所有解決方案也適用於 XML 反序列化。

例如,我們可以將 TypeReference 對象傳遞給 xmlMapper.readValue() 方法以解決該問題:

@Test
void givenXml_whenDeserializingWithTypeReference_thenGetExpectedList() 
  throws JsonProcessingException {
    String xml = readFile("/to-java-collection/books.xml");
    List<Book> bookList = xmlMapper.readValue(xml, new TypeReference<List<Book>>() {});
    assertThat(bookList.get(0)).isInstanceOf(Book.class);
    assertThat(bookList).isEqualTo(expectedBookList);
}

8. 結論

在本文中,我們探討了在使用 Jackson 解析 JSON 或 XML 時,可能遇到的 java.util.LinkedHashMap 無法轉換為 X 異常的原因。

隨後,我們通過示例介紹瞭解決該問題的方法。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.