Map 序列化和反序列化与 Jackson
上次更新:2024 年 1 月 8 日
1. 概述
在本快速教程中,我们将研究使用 Jackson 进行 Java Map 的序列化和反序列化。
我们将说明如何将 Map<String, String>、Map<Object, String> 和 Map<Object, Object> 序列化和反序列化为 JSON 格式的 String。
更多阅读
2. Maven 配置
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.2</version>
</dependency>
我们可以在 这里 获取 Jackson 的最新版本。
3. 序列化
序列化将 Java 对象转换为字节流,可以根据需要进行持久化或共享。Java Map 是将一个键 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,并使用 String 中的 toString() 方法将其写出为格式良好的 JSON String
{
"key" : "value"
}
3.2. Map<Object, String> 序列化
经过一些额外的步骤,我们也可以序列化包含自定义 Java 类的 map。让我们创建一个 MyPair 类来表示一对相关的 String 对象。
注意:getter/setter 应该为 public,并且我们使用 @JsonValue 注解 toString() ,以确保 Jackson 在序列化时使用这个自定义的 toString()
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 的 JsonSerializer 来告诉 Jackson 如何序列化 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 类 来满足我们的序列化需求。
接下来,我们使用 @JsonSerialize 注解将 MyPairSerializer 应用于我们的 Map<MyPair, String>。 请注意,我们只告诉 Jackson 如何序列化 MyPair ,因为它已经知道如何序列化 String:
@JsonSerialize(keyUsing = MyPairSerializer.class)
Map<MyPair, String> map;
然后让我们测试我们的 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>,但大部分工作已经完成。 让我们为我们的 map 使用 Jackson 的 MapSerializer ,为 map 的键和值类型使用来自上一节的 MyPairSerializer
@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);
}
进一步地,让我们序列化包含 Fruit 类实例作为键的 selectionByFruit Map
@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 来固定其访问和序列化期间的键的顺序。
最后,让我们也看看序列化包含 Fruit 类实例作为值的 selectionByPerson Map 的结果
@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 输入反序列化为不同签名的 Maps。
4.1. Map<String, String> 反序列化
对于一个简单的例子,让我们获取一个 JSON 格式的输入字符串并将其转换为 Java 集合 Map<String, String>
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 的类型。 这是我们的 map 的 toString() 表示形式
{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 反序列化 ClassWithAMap 中包含的 Map<MyPair, String>,所以我们需要扩展 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}
5. 结论
在本文中,我们学习了如何将 Java Maps 序列化为 JSON 格式的字符串,以及如何反序列化。
支持本文的代码可在 GitHub 上获取。 一旦你以 Baeldung Pro 会员 身份登录,就开始学习并在项目上进行编码。















