最近在使用apifox进行自动化接口测试时,发现当String, Integer等返回Null时,无法通过默认的返回数据结构校验。
image.png于是开始寻找响应解决方案,满足以下需求:
Boolean字段如果为null,输出为false,而非null
数值字段如果为null,输出为0,而非null
List字段如果为null,输出为[],而非null;
字符类型字段如果为null,输出为"",而非null
基于Fastjson的解决方案相对比较简单,但是Spring默认的Json处理框架是Jackson,如果替换为Fastjson,会导入引入一些额外问题,遇到过的比如有wx的反方授权回调接口参数解析错误,返回的字段外面多套了一层“”。
另外参考了maven repository 上 ,jackson的使用还是大大大于fastjson,且最近fastjson又有爆出安全漏洞,最终还是决定改回Jackson,基于Jackson来解决Null的返回值问题。
image.png试了几种方案,走了一些弯路,这里直接上最终方案吧。
自定义JacksonConfig类,设置返回所有字段,实现自定义BeanSerializerModifier,注入到SerializerFactory中,判断字段类型如果为列表、字符、数字、Boolean,则指定自定义的Null序列化实现
同时,由于Jackson针对日志时间处理的默认处理格式为yyyy-MM-ddTHH:mm:ss,在配置类中改为yyyy-MM-dd HH:mm:ss格式。
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.List;
import static cn.hutool.core.date.DatePattern.*;
/**
* Jackson全局配置
*
* @author ArchitectRoad
* @date 2022/6/20
*/
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
final ObjectMapper mapper = new ObjectMapper()
// 反序列化设置 关闭反序列化时Jackson发现无法找到对应的对象字段,便会抛出UnrecognizedPropertyException: Unrecognized field xxx异常
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
// 序列化设置 关闭日志输出为时间戳的设置
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
// 返回所有字段
.setSerializationInclusion(JsonInclude.Include.ALWAYS);
mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));
// mapper.enable(MapperFeature.USE_STD_BEAN_NAMING);
// mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
// DateFormat
mapper.setDateFormat(new SimpleDateFormat(NORM_DATETIME_PATTERN));
// LocalDate
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(
LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(NORM_DATE_PATTERN)));
javaTimeModule.addDeserializer(
LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(NORM_DATE_PATTERN)));
// LocalDateTime
javaTimeModule.addSerializer(
LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(NORM_DATETIME_PATTERN)));
javaTimeModule.addDeserializer(
LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(NORM_DATETIME_PATTERN)));
// LocalTime
javaTimeModule.addSerializer(
LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(NORM_TIME_PATTERN)));
javaTimeModule.addDeserializer(
LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(NORM_TIME_PATTERN)));
// 先注册自定义模块,再注册Java 8相关模块
mapper.registerModule(javaTimeModule);
mapper.registerModule(new Jdk8Module());
return mapper;
}
/**
* 处理数组类型的null值
*/
public class NullArrayJsonSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value == null) {
gen.writeStartArray();
gen.writeEndArray();
}
}
}
/**
* 处理字符串类型的null值
*/
public class NullStringJsonSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object o, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
gen.writeString(StringUtils.EMPTY);
}
}
/**
* 处理数字类型的null值
*/
public class NullNumberJsonSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object o, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
gen.writeNumber(0);
}
}
/**
* 处理布尔类型的null值
*/
public class NullBooleanJsonSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object o, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
gen.writeBoolean(false);
}
}
public class MyBeanSerializerModifier extends BeanSerializerModifier {
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
//循环所有的beanPropertyWriter
for (Object beanProperty : beanProperties) {
BeanPropertyWriter writer = (BeanPropertyWriter) beanProperty;
//判断字段的类型,如果是array,list,set则注册nullSerializer
if (isArrayType(writer)) {
//给writer注册一个自己的nullSerializer
writer.assignNullSerializer(new NullArrayJsonSerializer());
} else if (isNumberType(writer)) {
writer.assignNullSerializer(new NullNumberJsonSerializer());
} else if (isBooleanType(writer)) {
writer.assignNullSerializer(new NullBooleanJsonSerializer());
} else if (isStringType(writer)) {
writer.assignNullSerializer(new NullStringJsonSerializer());
}
}
return beanProperties;
}
/**
* 是否是数组
*/
private boolean isArrayType(BeanPropertyWriter writer) {
Class<?> clazz = writer.getType().getRawClass();
return clazz.isArray() || Collection.class.isAssignableFrom(clazz);
}
/**
* 是否是string
*/
private boolean isStringType(BeanPropertyWriter writer) {
Class<?> clazz = writer.getType().getRawClass();
return CharSequence.class.isAssignableFrom(clazz) || Character.class.isAssignableFrom(clazz);
}
/**
* 是否是int
*/
private boolean isNumberType(BeanPropertyWriter writer) {
Class<?> clazz = writer.getType().getRawClass();
return Number.class.isAssignableFrom(clazz);
}
/**
* 是否是boolean
*/
private boolean isBooleanType(BeanPropertyWriter writer) {
Class<?> clazz = writer.getType().getRawClass();
return clazz.equals(Boolean.class);
}
}
}
如果对你有帮助,请点赞
网友评论