背景
- 熟悉 Jackson 的同学应该都知道,JackSon 可以设置
JsonInclude 来控制序列化,可以设置 ALWAYS,NON_NULL,NON_ABSENT,NON_EMPTY,NON_DEFAULT,CUSTOM,USE_DEFAULTS
- 在当有复杂对象当默认值时,我们一般设置为
NON_DEFAULT 来避免序列化,减少反序列化时的内存消耗
复现
JsonInclude可以设置在类上,属性上,和全局上。一直以为设置的位置并不影响行为,只是写法不同罢了。全局设置最多是失效,但不会造成问题,但是最近工作中发现中并不是这般。请看如下例子
- A,B,C 均为同一个对象,enable 默认为 true,在 Instance 中手工关闭,理论均应输出
{"enable":false}
public class JSONTest {
public static void main(String[] args) throws JsonProcessingException {
System.out.println(stringfy(A.INSTANCE));
System.out.println(stringfy(B.INSTANCE));
System.out.println(stringfy(C.INSTANCE));
}
private static String stringfy(Object o) throws JsonProcessingException {
return new ObjectMapper().setDefaultPropertyInclusion(JsonInclude.Include.NON_DEFAULT)
.writeValueAsString(o);
}
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
static class A {
static A INSTANCE = new A().setEnable(false);
boolean enable = true;
public A setEnable(boolean enable) {
this.enable = enable;
return this;
}
public boolean isEnable() {
return enable;
}
}
static class B {
static B INSTANCE = new B().setEnable(false);
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
boolean enable = true;
public B setEnable(boolean enable) {
this.enable = enable;
return this;
}
public boolean isEnable() {
return enable;
}
}
static class C {
static C INSTANCE = new C().setEnable(false);
boolean enable = true;
public C setEnable(boolean enable) {
this.enable = enable;
return this;
}
public boolean isEnable() {
return enable;
}
}
}
- 但实际输出只有第一个是对的,其他两个均没输出,用户主动设置的属性就这么丢了,造成错误

原因
- 那么这是 Bug 么?我们可以看到在 jackson 项目的 issue 里也有人有同样的问题 github.com/FasterXML/j…
- 结论是,这是个 feature 并非 bug 。。。
- 参阅文档,可知
NON_DEFAULT 在不同上下文中表现其实是不一样的。
- 当在 POJO 的类上定义时,行为就是通常理解的 NON_DEFAULT
- 而在其他地方时,行为则是不为空,或者不为基础类型的默认值,或者 Date 不为 0

- 而上面的例子里,后两个因为 boolean 为基础类型,默认值为 false, 因此当前值等于默认值,就被 ignore 了。。。
解法
- 那么既然不是 bug,肯定不会修,怎么办呢?两种解法
- 所有类均要加上注解
- 利用 Jackson 的注解支持传递的特效,因此可以定义一个接口或者基类,只要继承即可
根因
- 既然都已经都发现了这个特殊的 feature,那么不妨继续看看其实怎么实现的?
首先,如何判断要不要输出呢?
- 在
com.fasterxml.jackson.databind.ser.BeanPropertyWriter#serializeAsField 中,我们可以看到 Jackson 在序列化字段时,会将当前值和_suppressableValue进行比较,如果一样即不输出。

如何获取 _suppressableValue ?
- 在
com.fasterxml.jackson.databind.ser.PropertyBuilder#buildWriter 中,我们可以看到如果 _useRealPropertyDefaults 为 true,则就用真实的对象 default,否则为 type 的 default

如何获得 _useRealPropertyDefaults ?
- 查看构造方法,即可看到其关键部分,便在于此处的一个 merge 操作。将类上的include注解 和 config 里的进行 merge, merge 实际操作为如果两个一个为null/EMPTY就取另一个,如果两个有冲突,就以后一个为准。
- 因为只有类上的
NON_DEFAULT才是RealPropertyDefaults,因此只用判断 inclPerType 即可
- 全局的a则是
_defaultInclusion,props上的c则是在 buildWriter 时用propDef.findInclusion()获得。
- 需要注意的是此处并没有判断属性上是否有
NON_DEFAULT,所以文档描述并不准确,当属性和类上同时定义了NON_DEFAULT,其行为也是不等于实际值,而不是不等于 type 的默认值
