Jackson中的 null 处理

1 概述

Jackson 是JSON序列化框架的一种,也是 Spring/SpringBoot 中内置推荐的基础框架,对于 JSON 的序列化有着非常好的性能以及易用的 API。

我们在开发过程中经常会碰到在序列化过程中的 null 问题,为了减少序列化后的数据大小,我们通常希望在序列化的过程中去掉那些 null 的数据,本文将介绍如何设置Jackson在序列化 java类时忽略空字段

2. 忽略类上的空字段

Jackson 框架支持通过在类上添加 @JsonInclude 注解来忽略为 null 的字段

1
2
@JsonInclude(Include.NON_NULL)
public class MyDto { ... }

也可以更细粒度的在要忽略的字段上添加 @JsonInclude 注解

1
2
3
4
5
6
7
8
9
public class MyDto {

@JsonInclude(Include.NON_NULL)
private String stringValue;

private int intValue;

// standard getters and setters
}

下面是相应的单元测试,可以看到 null 确实没有包含在最终的序列化输出中:

1
2
3
4
5
6
7
8
9
10
11
@Test
public void givenNullsIgnoredOnClass_whenWritingObjectWithNullField_thenIgnored()
throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
MyDto dtoObject = new MyDto();

String dtoAsString = mapper.writeValueAsString(dtoObject);

assertThat(dtoAsString, containsString("intValue"));
assertThat(dtoAsString, not(containsString("stringValue")));
}

3. 全局忽略空字段

Jackson还允许在ObjectMapper上全局配置忽略空字段,方法如下:

1
mapper.setSerializationInclusion(Include.NON_NULL);

设置后,就不需要在各个类或者字段上设置 @JsonInclude 注解了,所有的序列化都将统一忽略任何类中的任何字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void givenNullsIgnoredGlobally_whenWritingObjectWithNullField_thenIgnored()
throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
MyDto dtoObject = new MyDto();

String dtoAsString = mapper.writeValueAsString(dtoObject);

assertThat(dtoAsString, containsString("intValue"));
assertThat(dtoAsString, containsString("booleanValue"));
assertThat(dtoAsString, not(containsString("stringValue")));
}

4 Map 的处理

Map 比较特殊,包括了两个部分:Key - Value,要分别对待。

4.1 忽略Map 中 value 为 null 的数据

如果想要忽略 Map 中 value 为 null 的数据,那么需要用到前一节提到的 ObjectMapper 全局配置:mapper.setSerializationInclusion(Include.NON_NULL);,单元测试如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void givenIgnoringNullValuesInMap_whenWritingMapObjectWithNullValue_thenIgnored()
throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);

MyDto dtoObject1 = new MyDto();

Map<String, MyDto> dtoMap = new HashMap<String, MyDto>();
dtoMap.put("dtoObject1", dtoObject1);
dtoMap.put("dtoObject2", null);

String dtoMapAsString = mapper.writeValueAsString(dtoMap);

assertThat(dtoMapAsString, containsString("dtoObject1"));
assertThat(dtoMapAsString, not(containsString("dtoObject2")));
}

4.2 忽略Map 中 key 为 null 的数据

这种情况有点特殊,默认情况下,Jackson 是不支持有 null key 的 Map 的,如果你尝试序列化一个有 null key 的 Map 是会报错,抛出如下的异常:

1
2
3
c.f.j.c.JsonGenerationException: 
Null key for a Map not allowed in JSON (use a converting NullKeySerializer?)
at c.f.j.d.s.i.FailingSerializer.serialize(FailingSerializer.java:36)

不过 Jackson 框架提供了很好的自定义序列化机制,我们可以扩展一个 StdSerializer 来实现对于 null key 的支持,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyDtoNullKeySerializer extends StdSerializer<Object> {
public MyDtoNullKeySerializer() {
this(null);
}

public MyDtoNullKeySerializer(Class<Object> t) {
super(t);
}

@Override
public void serialize(Object nullKey, JsonGenerator jsonGenerator, SerializerProvider unused)
throws IOException, JsonProcessingException {
jsonGenerator.writeFieldName("");
}
}

我们重写了 serialize() 方法,当 key 为空时,我们通过 jsonGenerator.writeFieldName("") 指定了空字符串作为 key 来完成序列化工作。我们可以简单的测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void givenAllowingMapObjectWithNullKey_whenWriting_thenCorrect()
throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.getSerializerProvider().setNullKeySerializer(new MyDtoNullKeySerializer());

MyDto dtoObject = new MyDto();
dtoObject.setStringValue("dtoObjectString");

Map<String, MyDto> dtoMap = new HashMap<String, MyDto>();
dtoMap.put(null, dtoObject);

String dtoMapAsString = mapper.writeValueAsString(dtoMap);

assertThat(dtoMapAsString, containsString("\"\""));
assertThat(dtoMapAsString, containsString("dtoObjectString"));
}

5. 结论

序列化Map对象很常见,null 在我们的数据模型中也非常的重要且常见,Jackson提供了一些方便的自定义选项,可帮助您很好地自定义此序列化过程的输出。