FastJson 过滤复杂数据对象

1,404 阅读7分钟

前言

很久 没有 写博客了 一晃都已经2个月了 到新东家也有2个月了,好快!

博客还是要重新拾起了,养成一个好习至少要21天 但是毁掉它只需要一个借口~

描述

先描述下问题,最近做了一个任务,主要是处理数据的问题,所有的原始数据存在的我们的Nosql 数据库中,使用的是AWS 的DynamoDB, 因为要每个case 里面的数据 不能完整的交付给对方,所以只能选择性的交付!

分析

首先 我们经常看到 网上有几种处理方法,说真的 网上的文章 真的是copy copy 还是copy ,找了大半天也没找到 合适的方法,如果是简单的处理 :

  • 可以使用FastJson的注解 加上注解JSONField,并且设置serialize =fasle
  • 或者使用Java的关键字 transient

但是上面的2中 我的没法使用,每个表结构的scheme的的实体 已经封装在表里面了~我也不想重复造轮子了!

SimplePropertyPreFilter 的使用

后面我们网上很多文章都说了SimplePropertyPreFilter 这个过滤类,可以添加要展示的字段,也可以添加不要展示的字段!

但是 使用SimplePropertyPreFilter 没法满足我的使用,为什么呢?

使用SimplePropertyPreFilter 有2个问题

过滤名称重复的字段

可能说的比较抽象,那我写个demo 我相信小伙伴就能明白了:

{
	"contacts": [{
		"age": 30,
		"linkPhone": "05148891922",
		"name": "burgxun"
	}, {
		"age": 30,
		"linkPhone": "199888888",
		"name": "菲儿"
	}],
	"orderAmount": 890.23,
	"orderNO": "BU0000452",
    "name": "这个是特殊标记的名称",
	"resource": {
		"name": "苏州双飞日本",
		"producer": "同程国旅",
		"type": 1
	}
}

比如上面的json 我想去掉里面的name;

 SimplePropertyPreFilter simplePropertyPreFilter = new SimplePropertyPreFilter();
        simplePropertyPreFilter.getExcludes().add("name");
        String jsonString = JSON.toJSONString(order, simplePropertyPreFilter);
        System.out.println(jsonString);

执行的结果:

{
	"contacts": [{
		"age": 30,
		"linkPhone": "05148891922"
	}, {
		"age": 30,
		"linkPhone": "199888888"
	}],
	"orderAmount": 890.23,
	"orderNO": "BU0000452",
	"resource": {
		"producer": "同程国旅",
		"type": 1
	}
}

很明显的 看到问题了 把里面所有的name都去掉了

改良的版本

其实 有种改良的方法;

 SimplePropertyPreFilter simplePropertyPreFilter = new SimplePropertyPreFilter(TravelOrder.class);
        simplePropertyPreFilter.getExcludes().add("name");
        String jsonString = JSON.toJSONString(order, simplePropertyPreFilter);
        System.out.println(jsonString);

执行的结果:

{
	"contacts": [{
		"age": 30,
		"linkPhone": "05148891922",
		"name": "burgxun"
	}, {
		"age": 30,
		"linkPhone": "199888888",
		"name": "菲儿"
	}],
	"orderAmount": 890.23,
	"orderNO": "BU0000452",
	"resource": {
		"name": "苏州双飞日本",
		"producer": "同程国旅",
		"type": 1
	}
}

这个改良的版本 就是 指定 对应过滤器 使用在某个类上面

依旧不灵活的问题

这个虽然解决了 重复过滤的问题,但是如果这个时候 我需要过滤resource里面的producer,contacts里面的linkPhone和orderAmount字段;

这个时候 我们当然可以多些几个上面的SimplePropertyPreFilter 能达到这样的效果,但是这边有一个问题 就是要SimplePropertyPreFilter 要传入对应的class ;

为什么我要提到上面的class呢,因为我之前做的时候就是做了一个很大的弯路,我可以得到最外层的大对象的实体,但是在过滤里面的时候 如果这个非基础类型,这个时候再写一个SimplePropertyPreFilter 的时候,这个时候的class 获取就非常的困难了 我是通过反射去获取了 字段里面的实体类型!这里面还要经历过一段字段的查找 类型的判断 和获取等等~

很多人可能不明白 ,还是举例说明一下吧:

class TravelOrder {
    private String orderNO;
    private Float orderAmount;
    private String name;
    private Resource resource;
    private List<Passenger> contacts;
    ....
}
class Resource {
    private String name;
    private String producer;
    private Integer type;
    ...
}
class Passenger {
    private String name;
    private String linkPhone;
    private Integer age;
     ...
}

就像我上面说的 我要过滤 resource字段 里面的 producer 字段,这个时候 我只有传入的TravelOrder的Class,这个时候获取resource字段的类型 我就要反射手段去获取类型,然后去编写SimplePropertyPreFilter ,可能还有能不明白 为什么不直接写一个Resource.class 不就行了么 ! 如果这么写的话,我们要写很多这样的过滤SimplePropertyPreFilter如果每个类的过滤的字段不一样,我们要写很多重复的劳动,代码就不灵活了

最终版本

那么 既然有这么问题,我们先去看下SimplePropertyPreFilter 是怎么实现的 是否自己能定制化实现一个呢

public class SimplePropertyPreFilter implements PropertyPreFilter {
    private final Class<?> clazz;//过滤的目标类  如果不传入就默认是null 过滤对所有的类型有效
    private final Set<String> includes;// 要呈现的字段
    private final Set<String> excludes;// 不需要呈现的字段
    private int maxLevel;//过滤层级  就是我们的json的层级 有兴趣的可以自己测试下 就明白了
    public SimplePropertyPreFilter(String... properties) {
        this((Class)null, properties);
    }
    //返回true 说明是可以呈现   false 是不能呈现
    public boolean apply(JSONSerializer serializer, Object source, String name) {
        if (source == null) {
            return true;
        } else if (this.clazz != null && !this.clazz.isInstance(source)) {
            return true;//这边的逻辑是 clazz如果不是null 说明制定了使用的类型,如果这边isInstance是false 说明 此过滤对其应该无效 所以返回了true
        } else if (this.excludes.contains(name)) {
            return false;//主要就边和下面includes的判断
        } else {
            if (this.maxLevel > 0) {
                int level = 0;

                for(SerialContext context = serializer.context; context != null; context = context.parent) {
                    ++level;
                    if (level > this.maxLevel) {
                        return false;
                    }
                }
            }
            return this.includes.size() == 0 || this.includes.contains(name);
        }
    }
}

看了 上面的代码 我们知道 主要就是用这个传入的name去做判断的;

那我们测试下代码:

 SimplePropertyPreFilter simplePropertyPreFilter = new SimplePropertyPreFilter();
        simplePropertyPreFilter.getExcludes().add("orderAmount");
        simplePropertyPreFilter.getExcludes().add("resource.producer");
        simplePropertyPreFilter.getExcludes().add("contacts.linkPhone");
        String jsonString = JSON.toJSONString(order, simplePropertyPreFilter);
        System.out.println(jsonString);

看下结果:

{
	"contacts": [{
		"age": 30,
		"linkPhone": "05148891922",
		"name": "burgxun"
	}, {
		"age": 30,
		"linkPhone": "199888888",
		"name": "菲儿"
	}],
	"name": "这个是特殊标记的名称",
	"orderNO": "BU0000452",
	"resource": {
		"name": "苏州双飞日本",
		"producer": "同程国旅",
		"type": 1
	}
}

发现过滤的resource.producer,contacts.linkPhone 没有起作用 只有最外层的orderAmount被过滤掉了;

那既然 这样 那我们就修改下 apply里面的方法

过滤带层级属性 PropertyPreFilter

class MyPropertyPreFilter implements PropertyPreFilter {
    private final Class<?> clazz;
    private final Set<String> includes = new HashSet<>();
    private final Set<String> excludes = new HashSet<>();

    public MyPropertyPreFilter(String... properties) {
        this((Class) null, properties);
    }

    public MyPropertyPreFilter(Class<?> clazz, String... properties) {
        this.clazz = clazz;
        String[] var3 = properties;
        int var4 = properties.length;

        for (int var5 = 0; var5 < var4; ++var5) {
            String item = var3[var5];
            if (item != null) {
                this.includes.add(item);
            }
        }
    }
    public Set<String> getIncludes() {
        return includes;
    }
    public Set<String> getExcludes() {
        return excludes;
    }
    @Override
    public boolean apply(JSONSerializer jsonSerializer, Object source, String fieldName) {
        if (source == null) {
            return true;
        }
        if (clazz != null && !clazz.isInstance(source)) {
            return true;
        }

        SerialContext serialContext = jsonSerializer.getContext();
        String rankName = serialContext.toString();
        rankName = rankName + "." + fieldName;
        rankName = rankName.replace("$.", "");//去除开头的$.
        rankName = rankName.replaceAll("\\[\\d+\\]", "");//去除掉[] 如果是数组的话有有这个存在
        if (this.excludes.contains(rankName)) {
            return false;
        }

        if (includes.size() == 0 || includes.contains(rankName)) {
            return true;
        }

        return false;
    }
}

主要就是修改了 判断使用的name 带上了层级名称

        MyPropertyPreFilter myPropertyPreFilter = new MyPropertyPreFilter();
        myPropertyPreFilter.getExcludes().add("orderAmount");
        myPropertyPreFilter.getExcludes().add("resource.producer");
        myPropertyPreFilter.getExcludes().add("contacts.linkPhone");
        String jsonString = JSON.toJSONString(order, myPropertyPreFilter);
        System.out.println(jsonString);

运行的结果:

{"contacts":[{"age":30,"name":"burgxun"},{"age":30,"name":"菲儿"}],"name":"这个是特殊标记的名称","orderNO":"BU0000452","resource":{"name":"苏州双飞日本","type":1}}

其余的过滤器介绍

  • PropertyPreFilter:根据 PropertyName 判断是否序列化
  • PropertyFilter:根据 PropertyName 和 PropertyValue 来判断是否序列化
  • NameFilter:修改 Key,如果需要修改 Key,process 返回值则可
  • ValueFilter:修改 Value
  • BeforeFilter:序列化时在最前添加内容
  • AfterFilter:序列化时在最后添加内容

这里面的过滤器 我没有全部用到 只用到了PropertyPreFilter 和ValueFilter;

PropertyPreFilter 过滤器的使用场景我上面描述说了;

ValueFilter 过滤器 我是用在哪里呢,我们的数据为了存储的性能考虑,会把一些比较大的字符串,转成二进制的压缩后存储的Nosql 数据库中;这个时候 我们就不能直接使用户这个value了 就需要自己处理后 在呈现在jons字符串中。这个也很简单 只要实现ValueFilter接口就可以了!

总结

废话了那么多~也不知道能不能给你带来点儿收获!

这个项目中的工程 我改了好几版本,优化了好几个版本最终实现 还是比较通用化的实现也比较好理解~

最后顺带提一下 遇到问题 首先去看官方文档 如果实在找不到 也要找一个比较好的网站查看,百度出来的 真的是一推一推的 重复 copy 都不带改的~ 一点儿价值都没有~

希望这篇文章能给你在使用fastjson过滤方面带来帮助~

附录上fastjson GiitHub的wiki 地址:github.com/alibaba/fas…