引言:记录最近一次做项目过程中碰到的一个FastJson序列化的问题,本次项目基于spring boot实现,在接口返回数据的时候,实体类的序列化是由FastJson完成的,但是由于功能需要,我需要将某个实体类中的些为空的字段则不返回,但是不能改动FastJson作为序列化的大逻辑,也就是说不能将序列化由FastJson替换为JackSon,但是要实现Jackson中@JsonInclude注解的效果。
【注】FastJson默认是会将没赋值的属性不进行序列化,但是本项目中设置了FastJson将空值设为默认值的配置(例如:SerializerFeature.WriteNullNumberAsZero,SerializerFeature.WriteNullStringAsEmpty等),该配置不可更改,如果没配置,则本文内容就可以略过啦,你写的代码没有bug
解决方案:自定义注解+反射+自定义实现PropertyPreFilter接口实现
1、先简单介绍一下PropertyPreFilter接口
属性过滤器:使用PropertyPreFilter过滤属性
public interface PropertyPreFilter extends SerializeFilter {
boolean apply(JSONSerializer serializer, Object object, String name);
}
FastJson官方通过SimplePropertyPreFilter实现了该接口,可用于排除一些字段,简单使用如下
public class TestVo implements Serializable {
private static final long serialVersionUID = 4468516188008268414L;
private Long id;
private Integer age;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class TestFastJson {
public static void main(String[] args) {
TestVo testVo = new TestVo();
testVo.setId(1L);
testVo.setName("微雨");
SimplePropertyPreFilter preFilter = new SimplePropertyPreFilter();
preFilter.getExcludes().add("name");
System.out.println(JSONObject.toJSONString(testVo, preFilter, SerializerFeature.WriteNullNumberAsZero, SerializerFeature.WriteNullStringAsEmpty));
}
}
返回结果:{"age":0,"id":1}
age返回默认值,name被排除
粗略研究一下SimplePropertyPreFilter接口实现apply()方法的源码,可以得出以下结论:如果某个字段需要排除,则直接返回false就好了
public boolean apply(JSONSerializer serializer, Object source, String name) {
if (source == null) {
return true;
}
if (clazz != null && !clazz.isInstance(source)) {
return true;
}
// 重点代码,如果某个字段需要排除,则直接返回false就好了
if (this.excludes.contains(name)) {
return false;
}
if (maxLevel > 0) {
int level = 0;
SerialContext context = serializer.context;
while (context != null) {
level++;
if (level > maxLevel) {
return false;
}
context = context.parent;
}
}
if (includes.size() == 0 || includes.contains(name)) {
return true;
}
return false;
}
2、具体实现
思路:自定义一个注解,当序列化前,通过反射获取属性上是否有自定义注解,如果有,则获取属性的值,如果值为空则返回false,否则就返回true
自定义注解:
/**
* Description:该注解加在属性上,如果属性的值为空,则该属性不进行序列化返回
* 参考Jackson的@JsonInclude注解
*/
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomizeJsonExclude {
}
注入FastJsonHttpMessageConverter对象(controller层或方法如果加了@RestController或@ResponseBody注解,返回JSON数据的时候会自动调用fastjson进行序列化,不配置则默认采用StringHttpMessageConverter)
@Configuration
public class FastJsonConfiguration {
@Bean
public FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(
//保留map空的字段 | 如果保留的话,map 里面的值如果是null 则会保留null
// SerializerFeature.WriteMapNullValue,
// 将String类型的NULL转化为""
SerializerFeature.WriteNullStringAsEmpty,
// 将Number类型的NULL转化为0
SerializerFeature.WriteNullNumberAsZero,
// 将List类型的NULL转成[]
SerializerFeature.WriteNullListAsEmpty,
// 将Boolean类型的NULL转化为false
SerializerFeature.WriteNullBooleanAsFalse,
// 避免循环引用
SerializerFeature.DisableCircularReferenceDetect
);
ValueFilter valueFilter = (object, name, value) -> {
if (null == value){
value = "";
}
return value;
};
/**
* 关键代码,实现PropertyPreFilter中的apply()方法
* 参数说明:
* 1、serializer:不知道有啥用,没具体研究,有兴趣的小伙伴可自行研究
* 2、source:需要序列化的对象
* 3、name:对象中的字段名
*/
PropertyPreFilter preFilter = (serializer, source, name) -> {
if (source == null) {
return true;
}
Class<?> clazz = source.getClass();
try {
Field field = clazz.getDeclaredField(name);
if (!field.isAccessible()){
field.setAccessible(true);
}
// 判断字段上有没有自定义注解CustomizeJsonExclude
CustomizeJsonExclude annotation = field.getAnnotation(CustomizeJsonExclude.class);
if (Objects.nonNull(annotation)){
// 获取字段的值,判断是否为空
Object value = field.get(source);
return Objects.nonNull(value);
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
};
fastJsonConfig.setSerializeFilters(preFilter,valueFilter);
converter.setDefaultCharset(Charset.forName("UTF-8"));
converter.setFastJsonConfig(fastJsonConfig);
List<MediaType> mediaTypeList = new ArrayList<>();
// 解决中文乱码问题,相当于在Controller上的@RequestMapping中加了个属性produces = "application/json"
mediaTypeList.add(MediaType.APPLICATION_JSON);
converter.setSupportedMediaTypes(mediaTypeList);
return converter;
}
}
3、效果
public class FinalSearchResult implements SearchResult, Serializable {
private static final long serialVersionUID = -9084386156922106357L;
@CustomizeJsonExclude
private ProductSearchRepVo productRep;
@CustomizeJsonExclude
private String priceResult;
@CustomizeJsonExclude
private String articleResult;
public ProductSearchRepVo getProductRep() {
return productRep;
}
public void setProductRep(ProductSearchRepVo productRep) {
this.productRep = productRep;
}
public String getPriceResult() {
return priceResult;
}
public void setPriceResult(String priceResult) {
this.priceResult = priceResult;
}
public String getArticleResult() {
return articleResult;
}
public void setArticleResult(String articleResult) {
this.articleResult = articleResult;
}
}
返回结果:
返回结果达到预期