1. 概述
本教程将深入探讨如何使用 Jackson 库处理 Java Map 的序列化与反序列化。我们将通过实际案例演示如何将 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.13.3</version>
</dependency>
⚠️ 建议通过 Maven 中央仓库 获取最新版本。
3. 序列化
序列化将 Java 对象转换为字节流,便于持久化或网络传输。Java Map 作为键值对集合,其序列化常会遇到一些特殊场景。
3.1. Map<String, String>
序列化
最简单的场景,直接序列化字符串 Map:
Map<String, String> map = new HashMap<>();
map.put("key", "value");
ObjectMapper mapper = new ObjectMapper();
String jsonResult = mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(map);
ObjectMapper
是 Jackson 的核心序列化工具,通过 writerWithDefaultPrettyPrinter()
实现格式化输出:
{
"key" : "value"
}
3.2. Map<Object, String>
序列化
当 Map 的键为自定义对象时,需要额外处理。以 MyPair
类为例:
public class MyPair {
private String first;
private String second;
@Override
@JsonValue
public String toString() {
return first + " and " + second;
}
// 标准 getter/setter/equals/hashCode/构造方法
}
💡 关键点:
@JsonValue
注解确保序列化时使用自定义toString()
方法。
接着创建自定义序列化器:
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());
}
}
在 Map 上应用序列化器:
@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);
输出结果:
{
"Abbott and Costello" : "Comedy"
}
3.3. Map<Object, Object>
序列化
最复杂的场景是键值均为自定义对象。复用 MyPairSerializer
:
@JsonSerialize(keyUsing = MapSerializer.class)
Map<MyPair, MyPair> map;
@JsonSerialize(keyUsing = MyPairSerializer.class)
MyPair mapKey;
@JsonSerialize(keyUsing = MyPairSerializer.class)
MyPair mapValue;
测试代码:
mapKey = new MyPair("Abbott", "Costello");
mapValue = new MyPair("Comedy", "1940s");
map.put(mapKey, mapValue);
String jsonResult = mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(map);
输出:
{
"Abbott and Costello" : "Comedy and 1940s"
}
3.4. @JsonKey
注解
当对象作为 Map 键或值时需要不同序列化策略,使用 @JsonKey
解决:
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;
}
}
🔑
@JsonKey
指定作为键时使用name
字段,@JsonValue
控制作为值时的序列化。
测试用例:
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();
@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);
}
@Test
public void givenMapWithObjectKeys_WhenSerialize_ThenUseJsonKeyForSerialization()
throws JsonProcessingException {
Map<Fruit, String> selectionByFruit = new LinkedHashMap<>();
selectionByFruit.put(FRUIT1, "Hagrid");
selectionByFruit.put(FRUIT2, "Hercules");
String serializedValue = OBJECT_MAPPER.writeValueAsString(selectionByFruit);
Assertions.assertEquals("{\"Mango\":\"Hagrid\",\"Grapes\":\"Hercules\"}", serializedValue);
}
@Test
public void givenMapWithObjectValues_WhenSerialize_ThenUseJsonValueForSerialization()
throws JsonProcessingException {
Map<String, Fruit> selectionByPerson = new LinkedHashMap<>();
selectionByPerson.put("Hagrid", FRUIT1);
selectionByPerson.put("Hercules", FRUIT2);
String serializedValue = OBJECT_MAPPER.writeValueAsString(selectionByPerson);
Assertions.assertEquals("{\"Hagrid\":\"Alphonso Mango\",\"Hercules\":\"Black Grapes\"}",
serializedValue);
}
✅ 验证了对象在 Map 中作为键/值时采用不同序列化策略。
4. 反序列化
反序列化将 JSON 输入转换为 Java 对象,以下处理不同类型的 Map。
4.1. Map<String, String>
反序列化
基础场景,JSON 转字符串 Map:
String jsonInput = "{\"key\": \"value\"}";
TypeReference<HashMap<String, String>> typeRef
= new TypeReference<HashMap<String, String>>() {};
Map<String, String> map = mapper.readValue(jsonInput, typeRef);
📌 使用
TypeReference
明确目标类型,避免类型擦除问题。
输出:
{key=value}
4.2. Map<Object, String>
反序列化
处理自定义对象作为键的场景。首先为 MyPair
添加字符串解析构造方法:
public MyPair(String both) {
String[] pairs = both.split("and");
this.first = pairs[0].trim();
this.second = pairs[1].trim();
}
反序列化代码:
String jsonInput = "{\"Abbott and Costello\" : \"Comedy\"}";
TypeReference<HashMap<MyPair, String>> typeRef
= new TypeReference<HashMap<MyPair, String>>() {};
Map<MyPair,String> map = mapper.readValue(jsonInput, typeRef);
输出:
{Abbott and Costello=Comedy}
🛠️ 另一种方案:在包含 Map 的类中使用
KeyDeserializer
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 省略
}
自定义反序列化器:
public class MyPairDeserializer extends KeyDeserializer {
@Override
public MyPair deserializeKey(String key, DeserializationContext ctxt) {
return new MyPair(key);
}
}
测试:
String jsonInput = "{\"Abbott and Costello\":\"Comedy\"}";
ClassWithAMap classWithMap = mapper.readValue(jsonInput, ClassWithAMap.class);
4.3. Map<Object,Object>
反序列化
处理键值均为自定义对象的场景:
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);
输出:
{Abbott and Costello=Comedy and 1940s}
5. 总结
本文系统性地介绍了使用 Jackson 处理 Java Map 序列化与反序列化的核心技巧,包括:
- 基础字符串 Map 处理
- 自定义对象作为键/值的场景
@JsonKey
注解的灵活应用- 自定义序列化/反序列化器的实现
💻 完整示例代码请参考 GitHub 仓库。