有道无术,术尚可求,有术无道,止于术。
本系列Jackson 版本 2.17.0
本系列Spring Boot 版本 3.2.4
源码地址:https://gitee.com/pearl-organization/study-jaskson-demo
1. 问题场景
前端大佬👱:喂喂喂....你这个接口返回的字符串和集合怎么是null,导致我页面显示的都是undefined
后端大佬👴:本来就是null啊,你前端不会判断吗?
后端大佬👴:喂喂喂....你这个接口的请求参数怎么传是undefined ,不应该是null吗?
前端大佬👱:本来就是undefined啊,你后端不会判断吗?
前后端交互中的序列化/反序列化,可能会因为浏览器、后端运行环境、前后端框架限制等等原因,导致在处理一些特殊数据时,发生异常,我们需要了解这是怎么回事并处理。
2. 需求场景
当前用户列表查询接口,返回数据如下:
{
"id": 123456,
"roleList": [
"管理员",
"经理"
],
"birthday": "2024-04-16 11:26:55",
"name": "jack"
}
有时候并不能保证用户的每个信息都存在,例如角色集合、姓名、生日都有可能返回null:
{
"id" : 123456,
"roleList" : null,
"birthday" : null,
"name" : null
}
需要在序列化时进行特殊处理,比如:
- 字符串为
null时,返回空字符串“” - 集合为
null时,返回空集合
3. 案例演示
3.1 自定义序列化器
Jackson中的NullSerializer在序列化时,会将Java中的null、java.lang.Void写出为字面量的null值,所有需要自定义处理null的序列化器。
@JacksonStdImpl
@SuppressWarnings("serial")
public class NullSerializer
extends StdSerializer<Object>
{
public final static NullSerializer instance = new NullSerializer();
private NullSerializer() { super(Object.class); }
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeNull();
}
@Override
public void serializeWithType(Object value, JsonGenerator gen, SerializerProvider serializers,
TypeSerializer typeSer)
throws IOException
{
gen.writeNull();
}
@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException {
return createSchemaNode("null");
}
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException {
visitor.expectNullFormat(typeHint);
}
}
使用多个内部类自定义序列化器,用于处理不同的类型:
public class Jackson2NullValueSerializer {
/**
* 集合 null
*/
public static class NullArrayJsonSerializer extends StdSerializer<Object> {
public final static NullArrayJsonSerializer instance = new NullArrayJsonSerializer(Object.class);
protected NullArrayJsonSerializer() {
super(Object.class);
}
protected NullArrayJsonSerializer(Class<Object> t) {
super(t);
}
@Override
public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartArray(); // [
jsonGenerator.writeEndArray(); // ]
}
}
/**
* 字符串 null
*/
public static class NullStringJsonSerializer extends StdSerializer<Object> {
public final static NullStringJsonSerializer instance = new NullStringJsonSerializer(Object.class);
protected NullStringJsonSerializer() {
super(Object.class);
}
protected NullStringJsonSerializer(Class<Object> t) {
super(t);
}
@Override
public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString(""); // ""
}
}
}
3.2 自定义装饰器
继承BeanSerializerModifier重写changeProperties方法,解析完所有属性后,判断属性的类型,并添加对应的null值序列化器:
public class NullValueBeanSerializerModifier extends BeanSerializerModifier {
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
for (BeanPropertyWriter propertyWriter : beanProperties) {
Class<?> rawClass = propertyWriter.getType().getRawClass(); // 属性的真实类型
if (rawClass.isArray() || Collection.class.isAssignableFrom(rawClass)){
// 数组、集合类型时,注册 NullArrayJsonSerializer
propertyWriter.assignNullSerializer(Jackson2NullValueSerializer.NullArrayJsonSerializer.instance);
} else if ( CharSequence.class.isAssignableFrom(rawClass) || Character.class.isAssignableFrom(rawClass)) {
// 字符类型时,注册 NullStringJsonSerializer
propertyWriter.assignNullSerializer(Jackson2NullValueSerializer.NullStringJsonSerializer.instance);
}
}
return beanProperties;
}
}
3.3 配置
添加Spring配置类,注册自定义的ObjectMapper,并设置BeanSerializerModifier:
@Configuration
public class ObjectMapperConfig {
@Bean
@Primary
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
SerializerFactory serializerFactory = objectMapper
.getSerializerFactory()
.withSerializerModifier(new NullValueBeanSerializerModifier());
objectMapper.setSerializerFactory(serializerFactory);
return objectMapper;
}
}
注意:需要使用容器中的Jackson2ObjectMapperBuilder来构建,因为它加载了配置文件、消费了定制器。
3.4 测试
访问接口,输出如下:
{
"id": null,
"username": "",
"roleList": [],
"birthday": null
}