Contents

Jackson中Map序列化

1. 概述

在本快速教程中,我们将了解使用**Jackson 对 Java 映射进行序列化和反序列化**。 我们将说明如何在JSON 格式的字符串之间序列化和反序列化Map<String, String>Map<Object, String>Map<Object, Object>

2. Maven配置

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.11.1</version>
</dependency>

我们可以在这里 获得最新版本的 Jackson 。

3. 序列化

序列化将 Java 对象转换为字节流,可以根据需要进行持久化或共享。Java Maps是将键对象映射到值对象的集合,通常是最不直观的序列化对象。

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 字符串:

{
  "key" : "value"
}

3.2. *Map<Object, String>*序列化

通过一些额外的步骤,我们还可以序列化包含自定义 Java 类的映射。让我们创建一个MyPair类来表示一对相关的String对象。 注意:getter/setter 应该是公开的,我们用*@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 如何通过扩展 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 类 来满足我们的序列化要求。

接下来,我们使用 @JsonSerialize 注解将MyPairSerializer应用于我们的Map<MyPair, String>。请注意,我们只告诉 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"
}

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()*表示:

{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反序列化映射的键,即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映射的*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}