Jackson 2.x 系列【27】Spring Boot 集成之 Null 处理

310 阅读3分钟

有道无术,术尚可求,有术无道,止于术。

本系列Jackson 版本 2.17.0

本系列Spring Boot 版本 3.2.4

源码地址:https://gitee.com/pearl-organization/study-jaskson-demo

1. 问题场景

前端大佬👱:喂喂喂....你这个接口返回的字符串和集合怎么是null,导致我页面显示的都是undefined

后端大佬👴:本来就是null啊,你前端不会判断吗?

后端大佬👴:喂喂喂....你这个接口的请求参数怎么传是undefined ,不应该是null吗?

前端大佬👱:本来就是undefined啊,你后端不会判断吗?

image.png

前后端交互中的序列化/反序列化,可能会因为浏览器后端运行环境前后端框架限制等等原因,导致在处理一些特殊数据时,发生异常,我们需要了解这是怎么回事并处理。

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中的nulljava.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
}