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
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
上述方法在我們需要為特定字段自定義行為時非常有效。 但是,
PersonDTO readJsonWithLongForInts(String json) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.USE_LONG_FOR_INTS);
return mapper.readValue(json, PersonDTO.class);
}
在這裏,我們將在 ObjectMapper 讓我們測試此配置是否按預期工作: 當我們運行測試時,我們會看到它通過了。JSON 中的任何整數值都將反序列化為 Long 上述兩種方法都將所有整數值在 value 字段中的 Long 類型轉換為。 如果我們想動態地將值轉換為特定類型,可以使用 註解。 但是,這需要 JSON 輸入包含類型信息。 我們修改 JSON 結構以包含 value 字段的類型信息: 在這裏,我們添加了 type 字段到我們的對象中。 並且我們添加了 Integer 字段 age 以測試 int 和 long 值正確地被反序列化。 接下來,我們將 KeyValuePair 類修改為包含類型信息: 在這裏,我們使用 註解來指定 value 字段的類型信息。 我們指定外部屬性名稱為 的值包含類型信息。 我們還使用 註解來定義 value 可以有的所有子類型。 如果 type 字段的值為 ,則將其轉換為 類型的對象。 現在,當 Jackson 反序列化 value 時,它使用類型信息來確定 value 的確切類型。 讓我們編寫一個測試來驗證此行為: 當我們運行測試時,我們看到它成功通過。 值被轉換為 ,並且 值被轉換為 。 在本文中,我們學習瞭如何強制 Jackson 將 JSON 值反序列化為特定類型。我們探討了使用自定義反序列器、配置 ObjectMapper 使用反序列化功能以及使用 @JsonTypeInfo 註解來指定值字段的類型信息等不同方法。@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());
}
6. 使用
6.1. 向 JSON 添加類型
{
"person": [
{
"key": "name",
"type": "string",
"value": "John"
},
{
"key": "id",
"type": "long",
"value": 25
},
{
"key": "age",
"type": "int",
"value": 30
}
]
}
6.2. 自定義 DTO
public class PersonDTOWithType {
private List6.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. 結論