強制 Jackson 將 JSON 序列化為特定類型

Jackson
Remote
0
05:54 PM · Nov 30 ,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
    }
  ]
}

這裏我們有一個 person 對象,具有多個鍵值屬性。 value 字段可以具有不同類型的值。

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 並將其反序列化為 PersonDTO 對象:
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
}

當我們運行測試時,我們會看到它通過了。  Jackson 將 id 值反序列化為 Integer 類型,而不是 Long 類型,因為該值可以放入 Integer 類型中。

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

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

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

讓我們創建一個自定義 deserializer,用於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中獲取當前 token,並檢查它是否為數字或字符串。如果是數字,我們返回一個 Long 類型的值。如果是字符串,我們返回一個 String 類型的值。 這樣,我們強制 Jackson 將值反序列化為 long,如果它是一個數字。

接下來,我們將value字段在KeyValuePair類中使用 @JsonDeserialize 註解,以使用自定義 deserializer:

public static class KeyValuePair {
    private String key;

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

我們可以編寫另一個測試來驗證現在返回一個 Long 類型的值:

@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);
}

在這裏,我們將在 ObjectMapper USE_LONG_FOR_INTS 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. 使用 

上述兩種方法都將所有整數值在 value 字段中的 Long 類型轉換為。 如果我們想動態地將值轉換為特定類型,可以使用 註解。 但是,這需要 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
    }
  ]
}

在這裏,我們添加了 type 字段到我們的對象中。 並且我們添加了 Integer 字段 age 以測試 intlong 值正確地被反序列化。

6.2. 自定義 DTO

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

public class PersonDTOWithType {
    private List 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
    }
}

在這裏,我們使用 註解來指定 value 字段的類型信息。 我們指定外部屬性名稱為 的值包含類型信息。

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

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

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());
}

當我們運行測試時,我們看到它成功通過。 值被轉換為 ,並且 值被轉換為

7. 結論

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

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

發佈 評論

Some HTML is okay.