強制 Jackson 反序列化為特定類型
1. 概述
在本教程中,我們將探討如何強迫 Jackson 將 JSON 值反序列化為特定類型。
預設情況下,Jackson 將 JSON 值反序列化為目標欄位指定的類型。有時,目標欄位類型可能不特定。這樣做是為了允許多種類型的值。在這種情況下,Jackson 可以透過選擇指定類型的最接近匹配的子類型來反序列化該值。這可能會導致意想不到的結果。
我們將探討如何限制 Jackson 將 JSON 值反序列化為特定類型。
2. 程式碼範例設定
對於我們的範例,我們將定義一個 JSON 結構,其中的欄位可以具有多種類型的值。然後,我們將建立一個 Java 類別來表示 JSON 結構,並在某些情況下使用 Jackson 將值反序列化為特定類型。
2.1.依賴關係
讓我們先將Jackson Databind依賴項加入我們的pom.xml
檔中:
`
`
2.2. JSON結構
接下來,讓我們來看看輸入的 JSON 結構:
`{
"person": [
{
"key": "name",
"value": "John"
},
{
"key": "id",
"value": 25
}
]
}`
這裡我們有一個具有多個鍵值屬性的person
物件。 value
欄位可以有不同類型的值。
2.3.資料傳輸組織
接下來,我們將建立一個 DTO 類別來表示 JSON 結構:
`public class PersonDTO {
private List
// 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 by default
}`
當我們運行測試時,我們會看到它通過了。 Jackson 將id
值反序列化為Integer
類型而不是Long
類型,因為該值可以適合Integer
類型。
在接下來的部分中,我們將探討如何修改此預設行為。
4. 自訂反序列化為特定類型
強制 Jackson 將值反序列化為特定類型的最簡單方法是使用自訂反序列化器。
讓我們為KeyValuePair
類別中的value
欄位建立一個自訂反序列化器:
`public class ValueDeserializer extends JsonDeserializer
@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
獲取當前令牌並檢查它是數字還是字串。如果它是一個數字,我們以Long
類型傳回該值。如果它是字串,我們以String
類型傳回該值。透過這種方式,我們強制 Jackson 將值反序列化為long
(如果它是數字)。
接下來,我們將使用@JsonDeserialize
註解來註解KeyValuePair
類別中的value
字段,以使用自訂反序列化器:
`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
功能,強制 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
}
]
}`
這裡我們在物件中新增一個type
欄位。我們也加入了一個Integer
欄位age
來測試int
和long
值是否正確反序列化。
6.2.自訂 DTO
接下來,我們將修改KeyValuePair
類別以包含類型資訊:
`public class PersonDTOWithType {
private List
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
註解來指定value欄位的類型資訊。我們指定名稱為“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());
}`
當我們運行測試時,我們看到它成功通過了。 id
值轉換為Long
, age
值轉換為Integer.
七、結論
在本文中,我們學習如何強制 Jackson 將 JSON 值反序列化為特定類型。我們探索了不同的方法來實現此目的,例如使用自訂反序列化器、配置ObjectMapper
以使用反序列化功能以及使用@JsonTypeInfo
註解來指定值欄位的類型資訊。
與往常一樣,程式碼範例可在 GitHub 上取得。