为 Jackson 自定义序列化对象的 JSON 格式

伴随着 Play1, 我们原来使用的 JSON 库是 Gson. 回忆下 Gson 是怎么自定义序列化对象的 JSON 格式,大概是这样子的


GsonBuilder()..registerTypeHierarchyAdapter(Cat.class, new Cat());

然后 Cat 需要实现 JsonSerializer 的 serialize() 方法。

来到了 Play2 中,JSON 库变成了 Jackson,那么 Jackson 该如何为对象自定义 JSON 格式呢?

例如,默认时 Jackson 对 Map 类型输出的是一个 JSON 对象

Map("key1"->"value1", "key2"->"value2")     转换成 JSON 是 {"key1":"value1", "key2":"value2"}

当为适应某些客户端,对于 LinkedHashMap 类型,我们想要输出的是一个有序的 JSON 数组: [{"key1":"value1"},{"key2":"value2"}]

我们就应该自定义某些 Map 的序列化格式,实现方法有两种,addSerializer 和 @JsonSerialize,不管哪种方式都需事先具体化 JsonSerializer 类,并实现它的 serialize 抽象方法

所以我先来实现一个能序列化 Map 的 JsonArrayMapSerializer 类
 1package cc.unmi.serialization;
 2
 3import com.fasterxml.jackson.core.*;
 4import com.fasterxml.jackson.databind.*;
 5
 6import java.io.IOException;
 7import java.util.Map;
 8
 9public class JsonArrayMapSerializer extends JsonSerializer<Map<?, ?>> {
10
11  @Override
12  public void serialize(Map<?, ?> value, JsonGenerator jgen, SerializerProvider provider)
13      throws IOException, JsonProcessingException {
14    jgen.writeStartArray();
15    for(Object key: value.keySet()) {
16      jgen.writeStartObject();
17      jgen.writeObjectField(key.toString(), value.get(key));
18      jgen.writeEndObject();
19    }
20    jgen.writeEndArray();
21  }
22}

上面调用了 JsonGenerator 的 writeXxx() 方法来输出 JSON 格式

接着看两种实现方式

1. addSerializer() 方式

我采用了 Scala 来书写测试代码, 贴代码也有点问题,所以用图片代替

测试结果:

2. @JsonSerialize 注解方式

看下注解 @JsonSerialize 的定义

@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.TYPE, ElementType.PARAMETER})

它可以作用在注解,方法,字段,类型和参数上。我们举两个例子,分别是对类型和字段使用此注解

1)自定义的 JsonArrayMap 类型
1package cc.unmi.serialization;
2
3import com.fasterxml.jackson.databind.annotation.JsonSerialize;
4import java.util.LinkedHashMap;
5
6@JsonSerialize(using = JsonArrayMapSerializer.class)
7public class JsonArrayMap<K, V> extends LinkedHashMap<K, V>{
8
9}

2) 作用在字段上
1val map = new java.util.HashMap[String, String]
2map.put("key1", "value1")
3map.put("key2", "value2")
4
5val obj = new Object {
6  @JsonSerialize(using = classOf[JsonArrayMapSerializer])
7  val attr = map
8}

用测试用例来验证下:
 1package cc.unmi.serialization
 2
 3import com.fasterxml.jackson.databind.{PropertyNamingStrategy, ObjectMapper}
 4import utilities.JsonArrayMap
 5import com.fasterxml.jackson.databind.annotation.JsonSerialize
 6import org.scalatest.{MustMatchers, WordSpec}
 7
 8class JsonArrayMapSerializerTest extends WordSpec with MustMatchers {
 9
10  "JsonArrayMapSerializer#serialize" should {
11
12    val jsonSerializer: ObjectMapper = new ObjectMapper()
13      .setPropertyNamingStrategy(PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE)
14
15    "generate a JsonArrayMap as JSON array format" in {
16      val map = new JsonArrayMap[String, String]
17      map.put("key1", "value1")
18      map.put("key2", "value2")
19
20      val jsonString = jsonSerializer.writeValueAsString(map)
21      jsonString mustBe """[{"key1":"value1"},{"key2":"value2"}]"""
22    }
23
24    "write Map to json array if annotated with @JsonSerialize(using = classOf[JsonArrayMapSerializer]) " in
25      {
26        val obj = new {
27          @JsonSerialize(using = classOf[JsonArrayMapSerializer])
28          val field = new java.util.HashMap[String, String]
29          field.put("key1", "value1")
30          field.put("key2", "value2")
31        }
32
33        val jsonString = jsonSerializer.writeValueAsString(obj)
34        jsonString mustBe """{"Field":[{"key2":"value2"},{"key1":"value1"}]}"""
35      }
36  }
37}

测试结果

可以进一步考虑让 @JsonSerialize 应用在别处,如方法参数,getter 方法上。 永久链接 https://yanbin.blog/jackson-custom-serialize-json-format/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。