同一个 Controller 返回普通对象,接入 Activiti 7 之后,值为 null 的字段突然不再出现在 JSON 中。当时另建一个只接入 Spring Security 的工程对比,字段仍然存在,于是排查重点从安全过滤器转到了 JSON 序列化配置。
先看线上实际返回的字节
例如,一个对象中有名称,但备注为空,调用方可能期望:
{"name":"demo","remark":null}
如果序列化时采用忽略空值的策略,响应就会成为:
{"name":"demo"}
两种结果都是合法 JSON,但它们不一定符合相同的接口契约。“字段不存在”和“字段存在但为空”可能让前端走不同的分支,因此不能仅看 Java 对象里的值是否正确。
影响输出的是 Jackson 包含策略
当时搜索 NON_NULL 后,找到了相关配置项:
spring.jackson.default-property-inclusion=non_null
如果项目希望默认保留空值字段,可以在应用配置中显式设置:
spring.jackson.default-property-inclusion=always
历史项目中,这个覆盖恢复了需要的输出。它针对的是 Jackson 的属性包含策略,不是修改数据库值,也不是强行给所有空字符串赋值。
Spring Boot 的 Jackson 定制说明列出了这个属性及 always、non_null 等取值,并说明它作用于自动配置的 builder 及由它创建的 mapper。这里的说明以 Spring Boot 2.x、Jackson 2 的配置体系为范围,不代表所有 Activiti 版本都带有相同默认设置。
全局改动还是局部改动
如果所有接口都约定保留空值,统一配置比较清楚。如果只有某个 DTO 或字段有特殊要求,可以使用 Jackson 注解:
import com.fasterxml.jackson.annotation.JsonInclude;
public class DemoResponse {
@JsonInclude(JsonInclude.Include.ALWAYS)
public String remark;
}
注解表达的是这个属性的输出要求;应用中自定义 serializer、mixin 或响应包装仍可能影响最终结果。不要在多个位置反复设置相互矛盾的策略。
配置后仍不生效,继续看实际使用的转换器
先确认请求确实由 Jackson 输出。如果项目使用 Fastjson、手工拼接字符串,或定义了自己的 ObjectMapper 和消息转换器,仅修改 spring.jackson 属性可能没有覆盖实际对象。
随后检查生效的 profile、环境变量、外部配置文件、DTO 注解和自定义 mapper。用一个包含普通值、null、空字符串和空集合的响应进行对比,分别观察;null 与“空内容”也不是同一个概念。
最后回到接口契约决定取舍。让空字段重新出现并不天然更正确,关键是前后端对它的含义保持一致,并确认全局变化没有意外影响其他接口。