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 仓库


原始标题:Map Serialization and Deserialization with Jackson