1. 概述
本快速教程將介紹如何使用 Jackson 對 Java 映射數據進行序列化和反序列化。
我們將演示如何將 Map<String, String>, Map<Object, String> 和 Map<Object, Object> 序列化為 JSON 格式的字符串,以及反序列化為這些映射數據。
2. Maven 配置
...
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.2</version>
</dependency>我們可以在 這裏 獲取 Jackson 的最新版本。
3. 序列化
序列化將 Java 對象轉換為字節流,以便可以持久化存儲或根據需要進行共享。Java 中的 Maps 是一個將鍵 Object 映射到值 Object 的集合,並且通常是序列化時最不直觀的對象。
3.1. Map<String, String> 的序列化
為了一個簡單的例子,讓我們創建一個 Map<String, String> 並將其序列化為 JSON:
Map<String, String> map = new HashMap<>();
map.put("key", "value");
ObjectMapper mapper = new ObjectMapper();
String jsonResult = mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(map);ObjectMapper 是 Jackson 的序列化映射器。它允許我們序列化我們的 map,並使用 toString() 方法將它寫為格式良好的 JSON String。
{
"key" : "value"
}3.2. Map<Object, String> 的序列化
通過幾個額外的步驟,我們還可以序列化包含自定義 Java 類的 Map。 讓我們創建一個 MyPair</em/> 類來表示兩個相關的 String</em/> 對象。
注意:獲取器和設置器應為 public,並且我們應該使用 @JsonValue</em/> 註解 toString()</em/> 方法,以確保 Jackson 在序列化時使用此自定義 toString()</em/> 方法。
public class MyPair {
private String first;
private String second;
@Override
@JsonValue
public String toString() {
return first + " and " + second;
}
// standard getter, setters, equals, hashCode, constructors
}然後我們將告訴 Jackson 如何通過擴展 Jackson 的 JsonSerializer 來序列化 MyPair:
public class MyPairSerializer extends JsonSerializer<MyPair> {
private ObjectMapper mapper = new ObjectMapper();
@Override
public void serialize(MyPair value,
JsonGenerator gen,
SerializerProvider serializers)
throws IOException, JsonProcessingException {
StringWriter writer = new StringWriter();
mapper.writeValue(writer, value);
gen.writeFieldName(writer.toString());
}
}JsonSerializer,正如其名稱所示,使用 MyPair 的 toString() 方法,將 MyPair 序列化為 JSON。 此外,Jackson 提供了許多 Serializer 類,以滿足我們的序列化需求。
接下來,我們使用 MyPairSerializer 對我們的 Map<MyPair, String> 應用該類,並使用 @JsonSerialize 註解。 請注意,我們僅告訴 Jackson 如何序列化 MyPair,因為它已經知道如何序列化 String。
@JsonSerialize(keyUsing = MyPairSerializer.class)
Map<MyPair, String> map;然後我們來測試我們的地圖序列化:
map = new HashMap<>();
MyPair key = new MyPair("Abbott", "Costello");
map.put(key, "Comedy");
String jsonResult = mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(map);序列化的 JSON 輸出如下:
{
"Abbott and Costello" : "Comedy"
}3.3. Map<Object, Object>序列化
最複雜的情況是序列化 Map<Object, Object>,但大部分工作已經完成。我們使用 Jackson 的 MapSerializer 對我們的 map,以及上一節的 MyPairSerializer 用於 map 的鍵和值類型。
@JsonSerialize(keyUsing = MapSerializer.class)
Map<MyPair, MyPair> map;
@JsonSerialize(keyUsing = MyPairSerializer.class)
MyPair mapKey;
@JsonSerialize(keyUsing = MyPairSerializer.class)
MyPair mapValue;讓我們測試一下序列化我們的 Map<MyPair, MyPair>:
mapKey = new MyPair("Abbott", "Costello");
mapValue = new MyPair("Comedy", "1940s");
map.put(mapKey, mapValue);
String jsonResult = mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(map);使用 MyPair 的 toString() 方法序列化的 JSON 輸出如下:
{
"Abbott and Costello" : "Comedy and 1940s"
}3.4. @JsonKey 註解
當創建 Map 時,一個對象可以是鍵或值。 此外,當對象作為 Map 中的鍵出現與作為值出現時,我們可能需要不同的序列化策略。 因此,讓我們學習如何使用 @JsonKey 註解來實現這一點。
讓我們首先定義 Fruit 類,該類具有兩個成員,即 variety 和 name:
public class Fruit {
public String variety;
@JsonKey
public String name;
public Fruit(String variety, String name) {
this.variety = variety;
this.name = name;
}
@JsonValue
public String getFullName() {
return this.variety + " " + this.name;
}
}我們必須注意到,在任何 Fruit 對象作為 Map 鍵出現時,我們都希望使用它的名稱進行序列化。但是,當它作為值出現時,我們希望同時使用它的名稱和品種。
現在,讓我們初始化兩個 Fruit 類的實例和一個 ObjectMapper 類的實例,用於序列化目的:
private static final Fruit FRUIT1 = new Fruit("Alphonso", "Mango");
private static final Fruit FRUIT2 = new Fruit("Black", "Grapes");
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();接下來,我們應該記住,在序列化獨立實例時,會使用 @JsonValue 屬性進行序列化。讓我們驗證一下這對於這兩個對象是否成立:
@Test
public void givenObject_WhenSerialize_ThenUseJsonValueForSerialization()
throws JsonProcessingException {
String serializedValueForFruit1 = OBJECT_MAPPER.writeValueAsString(FRUIT1);
Assertions.assertEquals("\"Alphonso Mango\"", serializedValueForFruit1);
String serializedValueForFruit2 = OBJECT_MAPPER.writeValueAsString(FRUIT2);
Assertions.assertEquals("\"Black Grapes\"", serializedValueForFruit2);
}進一步來説,讓我們將 selectionByFruit Map,該 Map 包含 Fruit 類的實例,作為鍵進行序列化:
@Test
public void givenMapWithObjectKeys_WhenSerialize_ThenUseJsonKeyForSerialization()
throws JsonProcessingException {
// Given
Map<Fruit, String> selectionByFruit = new LinkedHashMap<>();
selectionByFruit.put(FRUIT1, "Hagrid");
selectionByFruit.put(FRUIT2, "Hercules");
// When
String serializedValue = OBJECT_MAPPER.writeValueAsString(selectionByFruit);
// Then
Assertions.assertEquals("{\"Mango\":\"Hagrid\",\"Grapes\":\"Hercules\"}", serializedValue);
}正如預期的那樣,@JsonKey 註解被用於此序列化。此外,我們使用了 LinkedHashMap 以在訪問和序列化期間修復鍵的順序。
最後,讓我們查看將 selectionByPerson Map(其中包含 Fruit 類的實例)序列化後的結果:
@Test
public void givenMapWithObjectValues_WhenSerialize_ThenUseJsonValueForSerialization()
throws JsonProcessingException {
// Given
Map<String, Fruit> selectionByPerson = new LinkedHashMap<>();
selectionByPerson.put("Hagrid", FRUIT1);
selectionByPerson.put("Hercules", FRUIT2);
// When
String serializedValue = OBJECT_MAPPER.writeValueAsString(selectionByPerson);
// Then
Assertions.assertEquals("{\"Hagrid\":\"Alphonso Mango\",\"Hercules\":\"Black Grapes\"}",
serializedValue);
}太棒了!我們已成功地根據對象在 Map 中作為鍵或值的角色,切換了序列化策略。
4. 反序列化
反序列化將字節流轉換為 Java 對象,以便在代碼中使用。 在本部分,我們將反序列化 JSON 輸入,將其轉換為具有不同簽名的 Map 對象。
4.1. Map<String, String> 反序列化
對於一個簡單的例子,我們以 JSON 格式的輸入字符串為例,將其轉換為一個 Map<String, String> Java 集合:
String jsonInput = "{\"key\": \"value\"}";
TypeReference<HashMap<String, String>> typeRef
= new TypeReference<HashMap<String, String>>() {};
Map<String, String> map = mapper.readValue(jsonInput, typeRef);我們使用 Jackson 的 ObjectMapper,正如我們在序列化中所做的那樣,使用 readValue() 處理輸入。 此外,請注意我們使用 Jackson 的 TypeReference,我們將在此處的所有反序列化示例中使用它來描述我們的目標 Map 的類型。 這是一個 toString() 表示形式的 Map:
{key=value}4.2. Map<Object, String> 反序列化
現在,讓我們更改我們的輸入 JSON 以及目標 TypeReference 的類型為 Map<MyPair, String>:
String jsonInput = "{\"Abbott and Costello\" : \"Comedy\"}";
TypeReference<HashMap<MyPair, String>> typeRef
= new TypeReference<HashMap<MyPair, String>>() {};
Map<MyPair,String> map = mapper.readValue(jsonInput, typeRef);我們需要創建一個構造函數 MyPair,該構造函數接受一個包含兩個元素的 String,並將它們解析為 MyPair 的元素:
public MyPair(String both) {
String[] pairs = both.split("and");
this.first = pairs[0].trim();
this.second = pairs[1].trim();
}我們的 Map<MyPair,String> 對象的 toString() 是:
{Abbott and Costello=Comedy}當我們將數據反序列化到包含 Map 的 Java 類時,我們可以使用 Jackson 的 KeyDeserializer 類,這是 Jackson 提供的眾多 反序列化 類之一。讓我們使用 @JsonCreator、@JsonProperty 和 @JsonDeserialize 註解我們的 ClassWithAMap 類。
public class ClassWithAMap {
@JsonProperty("map")
@JsonDeserialize(keyUsing = MyPairDeserializer.class)
private Map<MyPair, String> map;
@JsonCreator
public ClassWithAMap(Map<MyPair, String> map) {
this.map = map;
}
// public getters/setters omitted
}我們在這裏指示 Jackson 將 Map<MyPair, String> ,它包含在 ClassWithAMap 中,進行反序列化。因此,我們需要擴展 KeyDeserializer 以描述如何從輸入 String 中反序列化 map 的鍵,即 MyPair 對象。
public class MyPairDeserializer extends KeyDeserializer {
@Override
public MyPair deserializeKey(
String key,
DeserializationContext ctxt) throws IOException,
JsonProcessingException {
return new MyPair(key);
}
}然後我們可以使用 readValue 方法測試反序列化過程:
String jsonInput = "{\"Abbott and Costello\":\"Comedy\"}";
ClassWithAMap classWithMap = mapper.readValue(jsonInput,
ClassWithAMap.class);再次,ClassWithAMap 的 map 的 toString() 方法給我們提供了我們期望的輸出:
{Abbott and Costello=Comedy}4.3. Map<Object,Object> 反序列化
最後,我們將輸入 JSON 和目標 TypeReference 的類型更改為 Map<MyPair, MyPair>:
String jsonInput = "{\"Abbott and Costello\" : \"Comedy and 1940s\"}";
TypeReference<HashMap<MyPair, MyPair>> typeRef
= new TypeReference<HashMap<MyPair, MyPair>>() {};
Map<MyPair,MyPair> map = mapper.readValue(jsonInput, typeRef);我們的 Map<MyPair, MyPair> 對象的 toString() 方法是:
{Abbott and Costello=Comedy and 1940s}結論
在本文中,我們學習瞭如何將 Java 中的 Maps 序列化和反序列化為 JSON 格式的字符串。