知識庫 / JSON / Jackson RSS 訂閱

強制 Jackson 將數據反序列化為特定類型

Jackson
HongKong
6
09:42 PM · Dec 05 ,2025

1. 概述

本教程將探討如何強制 Jackson 將 JSON 值反序列化到特定的類型。

默認情況下,Jackson 會將 JSON 值反序列化到目標字段指定的類型。 有時,目標字段類型可能不明確,以便允許多種類型的值。 在這種情況下,Jackson 可能會通過選擇指定類型的最匹配的子類型來反序列化值。 這可能會導致意外結果。

我們將探討如何限制 Jackson 不反序列化 JSON 值到特定類型的操作。

2. 代碼示例設置

為了我們的示例,我們將定義一個包含可以具有多種類型值的字段的 JSON 結構。然後,我們將創建一個 Java 類來表示 JSON 結構,並使用 Jackson 在某些情況下將值反序列化為特定類型。

2.1. 依賴項

讓我們首先將 Jackson Databind 依賴項添加到我們的 pom.xml 文件中:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.2</version>
</dependency>

2.2. JSON 結構

接下來,讓我們看一下我們的輸入 JSON 結構:

{
  "person": [
    {
      "key": "name",
      "value": "John"
    },
    {
      "key": "id",
      "value": 25
    }
  ]
}

這裏有一個 對象,具有多個鍵值屬性。 字段可以包含不同類型的數值。

2.3. DTO

接下來,我們將創建一個 DTO 類來表示 JSON 結構:

public class PersonDTO {
    private List<KeyValuePair> person;

    // constructors, getters and setters
    
    public static class KeyValuePair {
        private String key;
        private Object value;

        // constructors, getters and setters
    }
}

PersonDTO 類包含一組鍵值對,代表一個人的信息。其中,value 字段的類型為 Object,以便允許多種類型的值。

3. 默認反序列化

為了演示我們的問題,我們來看一下 Jackson 中默認反序列化的工作方式。

3.1. 讀取 JSON

我們將定義一個方法,讀取我們的輸入 JSON 並將其反序列化為 <em >PersonDTO</em> 對象:

public PersonDTO readJson(String json) throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();
    return mapper.readValue(json, PersonDTO.class);
}

在這裏,我們使用 ObjectMapper 類讀取 JSON 字符串並將其轉換為 PersonDTO 對象。我們希望 id 字段的值被反序列化為 Long 類型。我們將編寫一個測試用例來驗證此行為。

3.2. 默認反序列化測試

現在,讓我們通過讀取 JSON 並檢查其字段的類型來測試我們的方法:

@Test
void givenJsonWithDifferentValueTypes_whenDeserialize_thenIntValue() throws JsonProcessingException {
    String json = "{\"person\": [{\"key\": \"name\", \"value\": \"John\"}, {\"key\": \"id\", \"value\": 25}]}";
    PersonDTO personDTO = readJson(json);
    assertEquals(String.class, personDTO.getPerson().get(0).getValue().getClass());
    assertEquals(Integer.class, personDTO.getPerson().get(1).getValue().getClass()); // Integer by default
}

當我們運行測試時,我們會發現它會通過。id 值反序列化為 Integer 類型,而不是 Long 類型,因為該值可以存儲在 Integer 類型中。

在下一部分,我們將探討如何修改此默認行為。

4. 自定義反序列化到特定類型

最簡單的方法是強制 Jackson 將值反序列化為特定類型,就是使用自定義反序列器。

讓我們創建一個自定義反序列器,用於value 字段在 KeyValuePair 類中的:

public class ValueDeserializer extends JsonDeserializer<Object> {
    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        JsonToken currentToken = p.getCurrentToken();
        if (currentToken == JsonToken.VALUE_NUMBER_INT) {
            return p.getLongValue();
        } else if (currentToken == JsonToken.VALUE_STRING) {
            return p.getText();
        } 
        return null;
    }
}

我們從 JsonParser 中獲取當前令牌,並檢查它是否為數字或字符串。如果是數字,則將其值作為<em>Long</em> 類型返回。如果是字符串,則將其值作為<em>String</em> 類型返回。 這樣就強制 Jackson 將值反序列化為<em>long</em>。

接下來,我們將<strong>在<em>KeyValuePair</em> 類中<em>value</em> 字段上添加<em>@JsonDeserialize</em> 註解,以使用自定義的反序列化器:

public static class KeyValuePair {
    private String key;

    @JsonDeserialize(using = ValueDeserializer.class)
    private Object value;
}

我們現在可以編寫另一個測試,以驗證一個<em>Long</em> 值是否被返回:

@Test
void givenJsonWithDifferentValueTypes_whenDeserialize_thenLongValue() throws JsonProcessingException {
    String json = "{\"person\": [{\"key\": \"name\", \"value\": \"John\"}, {\"key\": \"id\", \"value\": 25}]}";
    PersonDTOWithCustomDeserializer personDTO = readJsonWithCustomDeserializer(json);
    assertEquals(String.class, personDTO.getPerson().get(0).getValue().getClass());
    assertEquals(Long.class, personDTO.getPerson().get(1).getValue().getClass());
}

5. 配置 ObjectMapper

上述方法在我們需要為特定字段自定義行為時非常有效。然而,如果同一規則適用於類中的所有字段或多個類,我們可以配置 ObjectMapper 使用 USE_LONG_FOR_INTS 序列化特性。代碼塊圖片

PersonDTO readJsonWithLongForInts(String json) throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();
    mapper.enable(DeserializationFeature.USE_LONG_FOR_INTS);
    return mapper.readValue(json, PersonDTO.class);
}

在這裏,我們啓用USE_LONG_FOR_INTS特性,應用於ObjectMapper,強制 Jackson 將所有整數值序列化為Long類型。

讓我們測試這種配置是否按預期工作:

@Test
void givenJsonWithDifferentValueTypes_whenDeserializeWithLongForInts_thenLongValue() throws JsonProcessingException {
    String json = "{\"person\": [{\"key\": \"name\", \"value\": \"John\"}, {\"key\": \"id\", \"value\": 25}]}";
    PersonDTO personDTO = readJsonWithLongForInts(json);
    assertEquals(String.class, personDTO.getPerson().get(0).getValue().getClass());
    assertEquals(Long.class, personDTO.getPerson().get(1).getValue().getClass());
}

當我們運行測試時,我們會看到它通過。JSON 中的任何整數值都會被反序列化為 Long 類型。

6. 使用 @JsonTypeInfo  標籤

上述兩種方法將所有整數值從 value 字段轉換為 Long 類型。 如果想要動態地將值轉換為特定類型,我們可以使用 @JsonTypeInfo 標籤。 但是,這需要輸入 JSON 包含類型信息。

6.1. 為 JSON 添加類型信息

我們修改 JSON 結構,為 value 字段包含類型信息。

{
  "person": [
    {
      "key": "name",
      "type": "string",
      "value": "John"
    },
    {
      "key": "id",
      "type": "long",
      "value": 25
    },
    {
      "key": "age",
      "type": "int",
      "value": 30
    }
  ]
}

我們在此添加了類型字段到我們的對象中。我們還添加了年齡字段,用於測試intlong值是否正確地反序列化。

6.2. 自定義 DTO

接下來,我們將修改 KeyValuePair 類以包含類型信息:

public class PersonDTOWithType {
    private List<KeyValuePair> person;

    public static class KeyValuePair {
        private String key;

        @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
        @JsonSubTypes({
            @JsonSubTypes.Type(value = String.class, name = "string"),
            @JsonSubTypes.Type(value = Long.class, name = "long"),
            @JsonSubTypes.Type(value = Integer.class, name = "int")
        })
        private Object value;

        // constructors, getters and setters
    }
}

在這裏,我們使用 @JsonTypeInfo 註解來指定值字段的類型信息。我們指定一個名為 “type” 的外部屬性包含類型信息。

我們還使用 @JsonSubTypes 註解來定義值可以具有的所有子類型。如果 type 字段的值為 “string”,則將其轉換為 String 類型對象。

現在,當 Jackson 反序列化值時,它使用類型信息來確定值的確切類型。

6.3. 測試

讓我們編寫一個測試來驗證這種行為:

@Test
void givenJsonWithDifferentValueTypes_whenDeserializeWithTypeInfo_thenSuccess() throws JsonProcessingException {
    String json = "{\"person\": [{\"key\": \"name\", \"type\": \"string\", \"value\": \"John\"}, {\"key\": \"id\", \"type\": \"long\", \"value\": 25}, {\"key\": \"age\", \"type\": \"int\", \"value\": 30}]}";
    PersonDTOWithType personDTO = readJsonWithValueType(json);
    assertEquals(String.class, personDTO.getPerson().get(0).getValue().getClass());
    assertEquals(Long.class, personDTO.getPerson().get(1).getValue().getClass());
    assertEquals(Integer.class, personDTO.getPerson().get(2).getValue().getClass());
}

當我們運行測試時,我們看到它成功通過。<em id=""></em> 標識符值被轉換為 <em Long"></em>,而 <em age"></em> 值則被轉換為 <em Integer"></em>

7. 結論

在本文中,我們學習瞭如何強制 Jackson 將 JSON 值反序列化為特定類型。我們探討了實現這一目標的不同方法,例如使用自定義反序列器、配置 ObjectMapper 使用反序列化功能,以及使用 @JsonTypeInfo 註解指定值字段的類型信息。

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

發佈 評論

Some HTML is okay.