前言
很久 没有 写博客了 一晃都已经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…