springboot控制接口返回的字段_spring boot 接口返json 空字段处理

背景

java接口返json时,会有字段为空,客户端不希望有为null的字段。

实现

最终可行方案

另起一个配置类,继承 WebMvcConfigurerAdapter ,重写 configureMessageConverters ,并解决方法一中遇到的问题。

@ControllerAdvice

@Configuration

@Slf4j

public class WebConfig extends WebMvcConfigurerAdapter {

// 由于接口数量是有限的(当前背景下不到10个接口,故有10个不同对象),所以每一个接口给出对象的所有字段都存到内存中,永驻内存,避免每次都反射取字段

private Map> classFieldsMap = new ConcurrentHashMap<>();

@Override

public void configureMessageConverters(List> converters) {

//字符串转换器

StringHttpMessageConverter converter = new StringHttpMessageConverter(Charset.forName("UTF-8"));

converters.add(converter);

// FastJson转换器

//1.需要定义一个convert转换消息的对象;

FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();

//2.添加fastJson的配置信息,比如:是否要格式化返回的json数据;

FastJsonConfig fastJsonConfig = new FastJsonConfig();

fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat,

// 循环/重复引用问题,关闭引用监测

SerializerFeature.DisableCircularReferenceDetect,

// 将不是String类型的key转换成String类型

SerializerFeature.WriteNonStringKeyAsString);

// 将数字类型转0,字符串转"",list转空数组,其他(空Map、空对象等)转Object,表现上是转为了{}。

fastJsonConfig.setSerializeFilters((ValueFilter) (o, fieldName, source) -> {

if (source == null) {

List fieldList = this.getObjectAllFields(o) ;

Class clazz = Object.class;

for (Field field : fieldList) {

if (field.getName().equals(fieldName)) {

clazz = field.getType();

}

}

// Number是否是clazz的父类,或是否和clazz继承了相同父类

if (Number.class.isAssignableFrom(clazz)) {

return 0;

} else if (String.class.equals(clazz)){

return "";

} else if (List.class.equals(clazz)) {

return new int[]{};

} else {

return new Object();

}

}

return source;

});

//3处理中文乱码问题

List fastMediaTypes = new ArrayList<>();

fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);

//4.在convert中添加配置信息.

fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);

fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);

//5.将convert添加到converters当中.

converters.add(fastJsonHttpMessageConverter);

super.configureMessageConverters(converters);

}

@Override

public void extendMessageConverters(List> converters) {

log.info("converters size:"+converters.size());

for (HttpMessageConverter> messageConverter : converters) {

log.info(messageConverter.toString());

}

}

// 获取某对象的所有字段,如果内存中没有,则通过反射获取。

private List getObjectAllFields(Object o) {

List fieldList = classFieldsMap.get(o.getClass());

if (CollectionUtils.isEmpty(fieldList)) {

fieldList = new ArrayList<>();

Class tempClass = o.getClass();

while (tempClass != null) {

fieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));

tempClass = tempClass.getSuperclass();

}

classFieldsMap.put(o.getClass(), fieldList);

return fieldList;

}

return fieldList;

}

}

方法一 使用统一json配置 (未采用)

程序启动方法继承 WebMvcConfigurerAdapter , 重写 configureMessageConverters 。

使用该方法遇到的问题:

① 在使用过程中遇到map或者list值出现类似{"$ref":"$.data.list[0].batchInfo"} 的值。

网上搜索该现象为循环引用,解决方法可使用SerializerFeature.DisableCircularReferenceDetect。

循环引用:当一个对象包含另一个对象时,fastjson就会把该对象解析成引用。引用是通过$ref标示的,下面介绍一些引用的描述

"$ref":".." 上一级 "$ref":"@" 当前对象,也就是自引用 "$ref":"$" 根对象" $ref":"$.children.0" 基于路径的引用,相当于 root.getChildren().get(0)

② 基础对象(Integer、String、List、Map等)可以不输出null,但java实体类确不知道如何才能输出为{}。另外如果map的key为Integer,输出则也为数字(不使用该方法时json格式化会带引号,即为字符串),postman认为输出不可格式化,这块不知道是否会对客户端产生影响。

由于问题2未解决,故此方法未使用。

public class AppLauncher extends WebMvcConfigurerAdapter {

public static void main(String[] args) {

SpringApplication.run(AppLauncher.class);

}

@Override

public void configureMessageConverters(List> converters) {

super.configureMessageConverters(converters); // 不知道这句有什么用

FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();

FastJsonConfig fastJsonConfig = new FastJsonConfig();

fastJsonConfig.setSerializerFeatures(

SerializerFeature.PrettyFormat,

SerializerFeature.WriteMapNullValue, // 空map转{}

SerializerFeature.WriteNullNumberAsZero, // 空Integer转0

SerializerFeature.WriteNullStringAsEmpty, // 空String转""

SerializerFeature.WriteNullListAsEmpty // 空List转[]

);

// 处理中文乱码问题

List fastMediaTypes = new ArrayList<>();

fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);

fastConverter.setSupportedMediaTypes(fastMediaTypes);

fastConverter.setFastJsonConfig(fastJsonConfig);

//处理字符串, 避免直接返回字符串的时候被添加了引号——不知道这句有什么用

StringHttpMessageConverter smc = new StringHttpMessageConverter(Charset.forName("UTF-8"));

converters.add(smc);

}

}

方法二 针对部分可能为空的数据手动转map(采用)

该方法比较low,场景使用比较有限。但由于目前背景中,能确定只有几个对象可能为null,其他一定不为null。故可采用。

具体实现方式:1. 初始化赋值。2. 代码层面对map/list赋值过程中注意,如果为空则不赋值。(初始化已经为空map/list了)。3. 部分实体类使用工具类转为map。

public class ConvertUtil {

/**

* java对象转map

*/

public static Map javaBeanToMap(Object obj) {

if (obj == null) {

return MapUtils.emptyMap();

}

try {

// 通过intropestor分析出字节码对象的信息beaninfo

// 如果不想把父类的属性也列出来的话,那getBeanInfo的第二个参数填写父类的信息

// BeanInfo beanInfo = Introspector.getBeanInfo(user.getClass(), Object.class);

BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());

// 通过调用getPro....方法获取对象的属性描述器

PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();

// 网上找的代码中没有判空,点进源码中看了下注释,是可能为空的,所以此处做判空校验

// 实现类 SimpleBeanInfo 直接返的null,其他实现类返的数组

if (propertyDescriptors == null || propertyDescriptors.length <= 0) {

return MapUtils.emptyMap();

}

Map map = new HashMap<>(propertyDescriptors.length);

for (PropertyDescriptor property : propertyDescriptors) {

String key = property.getName();

// 过滤掉class属性

if (key.compareToIgnoreCase("class") == 0) {

continue;

}

Method getter = property.getReadMethod();

Object value;

if (getter != null) {

value = getter.invoke(obj);

if (value == null) {

value = MapUtils.emptyMap();

}

} else {

value = MapUtils.emptyMap();

}

map.put(key, value);

}

return map;

} catch (Exception e) {

log.error("javaBeanToMap failed : {}", e.getMessage());

return MapUtils.emptyMap();

}

}

}

在这之前用了个比较low的方法,这里面是反射获取的类的所有方法,然后获取属性名的时候是根据get切割来的,众所周知,get方法会把属性名首字母大写,于是又有了把首字母转为小写各种切割拼接,感觉很不友好,所以用了上面的方法。low方法如下:

public class ConvertUtil {

public static Map javaBeanToMap(Object obj) {

// 这里map初始化还不能设置初始值,因为不知道设为几,不友好

Map map = new HashMap<>();

List methods = getAllMethods(obj);

for (Method m : methods) {

String methodName = m.getName();

if (methodName.startsWith("get")) {

try {

//获取属性名,首字母小写

String propertyName = methodName.substring(3);

propertyName = (new StringBuilder()).append(Character.toLowerCase(propertyName.charAt(0)))

.append(propertyName.substring(1)).toString();

if (Objects.isNull(m.invoke(obj))) {

map.put(propertyName, MapUtils.emptyMap());

} else {

map.put(propertyName, m.invoke(obj));

}

} catch (Exception e) {

log.error("javaBeanToMap failed : {}", e.getMessage());

return MapUtils.emptyMap();

}

}

}

return map;

}

/**

* 获取obj中的所有方法

*/

private static List getAllMethods(Object obj) {

List methods = new ArrayList<>();

Class> clazz = obj.getClass();

while (!clazz.getName().equals("java.lang.Object")) {

methods.addAll(Arrays.asList(clazz.getDeclaredMethods()));

clazz = clazz.getSuperclass();

}

return methods;

}

}

方法三 写配置类

该方法会把所有未空的都转为"",个人测试Integer、String、Map、List,都输出为了"",不是我所想要的输出。

@Configuration

public class JacksonConfig {

@Bean

@Primary

@ConditionalOnMissingBean(ObjectMapper.class)

public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {

ObjectMapper objectMapper = builder.createXmlMapper(false).build();

SerializerProvider serializerProvider = objectMapper.getSerializerProvider();

serializerProvider.setNullValueSerializer(new JsonSerializer() {

@Override

public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)

throws IOException, JsonProcessingException {

jsonGenerator.writeString("");

}

});

return objectMapper;

}

}

测试接口返回数据对象Vo中添加未赋值的各种类型:

public class VoA extends VoB {

private XXVo testBean;

private Map testMap;

private List testList;

private Integer testInt;

private String testString;

}

输出结果(postman截图):

方法四 空对象不输出

在该背景下,其实null对象不输出也是一个不错的办法,但是由于null不输出会导致一些问题,比如:list中多个对象,有的对象有A字段,有的对象没有A字段,这个我们认为不方便测试也不好维护,故没有采用。但可能以后会有需要,此处记一笔。

实现方式为,在类上加注解@JsonInclude(JsonInclude.Include.NON_NULL)

测试:接口返回数据对象Vo中添加未赋值的各种类型

@JsonInclude(JsonInclude.Include.NON_NULL)

public class VoA extends VoB {

private XXVo testBean;

private Map testMap;

private List testList;

private Integer testInt;

private String testString;

}

输出结果:

没有各属性字段

注意!!踩坑:

可以看到上面我的VoA继承了VoB,且@JsonInclude(JsonInclude.Include.NON_NULL)注解在VoA上。如果这些test属性写在了VoB里,且注解在VoA上,那么该注解是不生效的,也就是各属性都会输出且为null。

具体解决方案没有进一步尝试,不过猜测应该是在VoB上也加上@JsonInclude(JsonInclude.Include.NON_NULL)注解就可以了。

关于继承的坑,想起对象的toString()方法也遇到过一个小坑。现在为了代码简便,实体类对象都使用了package lombok.@Data注解,写后台的时候,部分更新操作的请求对象都打了info日志,以便出问题可以排查,无意间发现A继承了B,打印A的时候,B的属性并没有打印出来。查了原因发现是@Data注解生成的toString()方法是默认不包含父类的,所以要在A上面加上package lombok.@ToString(callSuper=true)注解。还好我这是管理后台,日志没有很常用,如果是服务日志要打印全,一定要注意。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值