Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE]Filter功能兼容fastjson建议 #2590

Closed
GYung opened this issue May 17, 2024 · 11 comments
Closed

[FEATURE]Filter功能兼容fastjson建议 #2590

GYung opened this issue May 17, 2024 · 11 comments
Labels
bug Something isn't working enhancement New feature or request
Milestone

Comments

@GYung
Copy link

GYung commented May 17, 2024

请描述您的需求或者改进建议

最近在做fastjson升级到fastjson2改造,由于是网关应用需要对JSON数据进行修改和裁剪,所以会大量用到SerializeFilter扩展能力,在升级到fastjson2的FIlter时,发现以下功能是不兼容的:

  1. ValueFilter 和 PropertyFilter 方法签名冲突

方法名都是apply, 导致无法在一个类里面实现这两个接口

  1. 无法通过PropertyPreFilter.apply构建当前对象的完整路径

fastjson的PropertyPreFilter.apply的入参JSONSerializer,我可以基于JSONSerializer.context的parent和fieldName来构建出当前对象的完整路径。
而fastjson2的PropertyPreFilter.apply入参JSONWriter没法支持我能构建出当前对象的完整路径
(因为我这边需要通过参数完整路径来定位到该参数相关业务配置)

请描述你建议的实现方案

1.如果两个filter功能不是互斥,是否可以考虑使用不同方法名

2.JSONWriter的refs提供一个get方法获取Path, Path再提供获取parent和name的get方法,这种是否可行的吗?

描述您考虑过的替代方案

1.目前是通过两个类来分别继承两个接口,然后两个类再互相绑定用于交换数据

附加信息

@GYung GYung added the enhancement New feature or request label May 17, 2024
@wenshao
Copy link
Member

wenshao commented May 17, 2024

这个是个问题,你能帮提供下更具体一点的代码么?另外这个周末出去玩,估计要晚几天才能处理哈

@GYung
Copy link
Author

GYung commented May 18, 2024

fastjson中实现参数过滤示例

public class FilterDemo implements PropertyPreFilter, PropertyFilter, ValueFilter {

    private final IdentityHashMap<Object, String> objectPathMap = new IdentityHashMap<>();

    private String paramPath;

    private final Boolean NOT_FILTER = true;

    private final Boolean FILTER = false;

    /**
     * 步骤1-构建参数路径
     * 继承自 com.alibaba.fastjson.serializer.PropertyPreFilter
     */
    @Override
    public boolean apply(JSONSerializer serializer, Object object, String name) {
        // 获取当前对象的路径 e.g result.shopInfo
        String parentPath = getAndSetObjectPath(serializer.getContext());
        // 设置当前对象属性的参数路径 e.g result.shopInfo.picUrl
        this.paramPath = parentPath + "." + name;

        return NOT_FILTER;
    }

    /**
     * 基于SerialContext构建参数路径
     */
    private String getAndSetObjectPath(SerialContext context) {
        // 生成参数路径
        generateObjectPath(context, new StringBuilder());

        return objectPathMap.get(context.object);
    }

    private void generateObjectPath(SerialContext context, StringBuilder sb) {
        if (objectPathMap.containsKey(context.object)) {
            sb.append(objectPathMap.get(context.object));
            return;
        }
        // 递归查找父节点路径
        generateObjectPath(context.parent, sb);
        // 设置当前节点路径
        sb.append(".").append(context.fieldName);
        objectPathMap.put(context.object, sb.toString());
    }

    /**
     * 步骤2-执行参数过滤
     * 继承自com.alibaba.fastjson.serializer.PropertyFilter
     */
    @Override
    public boolean apply(Object object, String name, Object value) {
        // 根据参数路径查询参数配置
        Object paramConfig = getParamConfig(this.paramPath);
        return Objects.isNull(paramConfig) ? FILTER : NOT_FILTER;
    }

    /**
     * 步骤3-处理参数数据
     * 继承自com.alibaba.fastjson.serializer.ValueFilter
     */
    @Override
    public Object process(Object object, String name, Object value) {
        Object paramConfig = getParamConfig(this.paramPath);
        return value;
    }
}

而fastjson2中PropertyPreFilter,无法通过JSONWriter提供信息来构建路径

public interface PropertyPreFilter extends Filter {
    boolean process(JSONWriter writer, Object source, String name);
}

辛苦大佬帮忙看看解法

@GYung
Copy link
Author

GYung commented May 22, 2024

对于问题2
最近尝试通过PropertyFilter接口方法来构建参数路径,因为value是object的属性,所以可以实现类似SerialContext的父子对象关联。

public interface PropertyFilter  extends Filter {
    boolean apply(Object object, String name, Object value);
}

但是对于存在集合的情况行不通
因为集合遍历时没有回调apply方法,则集合的元素和集合之间无法建立关联,从而无法构建出集合参数的路径

比如:

{"result":{"shopInfoList":[{"name":"名称","url":"xxxx"}]}}

1.当回调apply方法,value是shopInfoList时

// object=result name=shopInfoList value=List
apply(Object object, String name, Object value);

2.下一次回调apply方法,object就是集合第一个元素,object的父对象是谁不得而知

// object=shopInfo name=name value=名称
apply(Object object, String name, Object value);

@wenshao 麻烦帮忙看看fastjson2中有什么办法能够支持构建参数路径?或者能像fastjson的SerialContext一样存储对象间的父子关系

@wenshao
Copy link
Member

wenshao commented May 22, 2024

https://oss.sonatype.org/content/repositories/snapshots/com/alibaba/fastjson2/fastjson2/2.0.51-SNAPSHOT/
请帮忙用2.0.51-SNAPSHOT版本验证,JSONWriter新增加了getPath方法,看是否满足你的需求,使用如下:

   @Test
    public void test() {
        Bean bean = new Bean();
        bean.item0 = new Item(101);
        bean.item1 = new Item(102);
        bean.item2 = new Item(103);

        FilterDemo filter = new FilterDemo();
        String json = JSON.toJSONString(bean, filter, JSONWriter.Feature.ReferenceDetection);
        assertEquals("{\"item0\":{\"id\":101},\"item1\":{\"id\":102},\"item2\":{}}", json);
    }

    public static class FilterDemo
            implements PropertyPreFilter {
        @Override
        public boolean process(JSONWriter writer, Object source, String name) {
            String parentPath = writer.getPath();
            if (parentPath.startsWith("$.item2")) {
                return false;
            }
            return true;
        }
    }

    public static class Bean {
        public Item item0;
        public Item item1;
        public Item item2;
    }

    public static class Item {
        public int id;

        public Item() {
        }

        public Item(int id) {
            this.id = id;
        }
    }
``

@wenshao wenshao added this to the 2.0.51 milestone May 22, 2024
@GYung
Copy link
Author

GYung commented May 23, 2024

Map和非List集合里面的元素getPath不太符合预期
例如process方法的source参数是childItem时,调用writer.getPath()
预期:$.map1.childItem
实际:$.map1

    private Bean bean;
    @Before
    public void setUp() {
        bean = new Bean();
        bean.item1 = new Item(101);
        bean.item2 = new Item(102);
        bean.set1 = new HashSet<Item>(){{add(new Item(201));}};
        bean.map1 = new HashMap<String, Item>(){{put("childItem", new Item(301));}};
    }

    @Test
    public void test_map1_childItem() {
        FilterDemo filter = new FilterDemo("$.map1.childItem");
        String json = JSON.toJSONString(bean, filter, JSONWriter.Feature.ReferenceDetection);
        org.junit.Assert.assertEquals("{\"item1\":{\"id\":101},\"item2\":{\"id\":102},\"map1\":{},\"set1\":[{\"id\":201}]}"
            , json);
    }

    @Test
    public void test_set1_0() {
        FilterDemo filter = new FilterDemo("$.set1[0]");
        String json = JSON.toJSONString(bean, filter, JSONWriter.Feature.ReferenceDetection);
        org.junit.Assert.assertEquals("{\"item1\":{\"id\":101},\"item2\":{\"id\":102},\"map1\":{\"childItem\":{\"id\":301}},\"set1\":[{}]}"
            , json);
    }

    public static class FilterDemo implements PropertyPreFilter {
        private final String filterPath;

        public FilterDemo(String filterPath) {
            this.filterPath = filterPath;
        }

        @Override
        public boolean process(JSONWriter writer, Object source, String name) {
            String parentPath = writer.getPath();
            if (filterPath != null && parentPath.startsWith(filterPath)) {
                return false;
            }
            return true;
        }
    }

    public static class Bean {
        public Item item1;
        public Item item2;
        public Set<Item> set1;
        public Map<String, Item> map1;
    }

    public static class Item {
        public int id;

        public Item() {
        }

        public Item(int id) {
            this.id = id;
        }
    }

@wenshao
Copy link
Member

wenshao commented May 24, 2024

这个时候,还没对Property求值,path是参数source对象的path,process方法有参数 name。

@GYung
Copy link
Author

GYung commented May 24, 2024

fastjson,打印serializer.getContext().getPath()路径

$
$.map1
$.map1.childItem

fastjson2,打印writer.getPath()路径

$
$.map1
$.map1

fastjson2的测试方法

   @Test
    public void test_map1_childItem() {
        Bean  bean = new Bean();
        bean.map1 = new HashMap<String, Item>() {{put("childItem", new Item(301));}};

        List<String> paths = new ArrayList<>();
        FilterDemo filter = new FilterDemo(paths);

        JSON.toJSONString(bean, filter, JSONWriter.Feature.ReferenceDetection);
        org.junit.Assert.assertTrue(paths.contains("$.map1.childItem"));
    }

    public static class FilterDemo implements PropertyPreFilter {
        private final List<String> paths;

        public FilterDemo(List<String> paths) {
            this.paths = paths;
        }

        @Override
        public boolean process(JSONWriter writer, Object source, String name) {
            paths.add(writer.getPath());
            return true;
        }
    }

    public static class Bean {
        public Map<String, Item> map1;
    }

    public static class Item {
        public int id;

        public Item() {
        }

        public Item(int id) {
            this.id = id;
        }
    }

可能原因分析

1. map1处理前先经过FieldWriterObject,里面会调setPath来更新path
2. 接着由ObjectWriterImplMap处理map1,遍历map1和回调process(),此时source对象是map1
3. 然后处理map1中value(childItem), 会调用ObjectWriterAdapter的writeWithFilter(),该方法会回调process(),而此次回调前没有更新过path
4. 最终步骤3中process()source对象是childItem,但是其path还是$.map1

@GYung
Copy link
Author

GYung commented May 30, 2024

@wenshao 上面这个麻烦再帮忙看看

@wenshao
Copy link
Member

wenshao commented May 30, 2024

https://oss.sonatype.org/content/repositories/snapshots/com/alibaba/fastjson2/fastjson2/2.0.51-SNAPSHOT/
@GYung 问题修复了,请帮忙更新2.0.51-SNAPSHOT版本再验证下

@wenshao wenshao added the bug Something isn't working label May 30, 2024
@wenshao wenshao added the fixed label May 30, 2024
@GYung
Copy link
Author

GYung commented May 31, 2024

还是不太对,当前fastjson2输出

$
$.map1.childItem
$.map1.childItem

fastjson输出

$
$.map1
$.map1.childItem

修正代码在ObjectWriterImplMap遍历map1过程中更新path,会导致process()的source是map1,但path已经指向childItem

  // com.alibaba.fastjson2.writer.ObjectWriterImplMap#writeWithFilter
  for (Map.Entry entry : (Iterable<Map.Entry>) map.entrySet()) {
            Object value = entry.getValue();
            if (value == null && !writeNulls) {
                continue;
            }

            Object entryKey = entry.getKey();
            String key;
            if (entryKey == null) {
                key = null;
            } else {
                key = entryKey.toString();
            }

            String refPath = null;
            if (refDetect) {
                // 1、这里将path指向childItem($.map1.childItem)
                refPath = jsonWriter.setPath(key, value);
                if (refPath != null) {
                    jsonWriter.writeName(key);
                    jsonWriter.writeReference(refPath);
                    jsonWriter.popPath(value);
                    continue;
                }
            }

            try {
                if (propertyPreFilter != null) {
                    // 2、这里的object是map1 , key是childItem 
                   //  按照"path是参数source对象的path",  期望$.map1  实际$.map1.childItem
                    if (!propertyPreFilter.process(jsonWriter, object, key)) {
                        continue;
                    }
                }
              // 省略...
               if (value == null) {
                    jsonWriter.writeNull();
                } else {
                    Class<?> valueType = value.getClass();
                    ObjectWriter valueWriter = jsonWriter.getObjectWriter(valueType);
                    // 3、是否可以在valueWriter.write()方法里面去更新path?
                    valueWriter.write(jsonWriter, value, fieldName, fieldType, this.features);
                }
            } finally {
                if (refDetect) {
                    jsonWriter.popPath(value);
                }
            }
        }

@wenshao

@wenshao
Copy link
Member

wenshao commented Jun 1, 2024

https://github.com/alibaba/fastjson2/releases/tag/2.0.51
请用新版本,剩余问题继续修复

@wenshao wenshao closed this as completed Jun 1, 2024
@wenshao wenshao modified the milestones: 2.0.51, 2.0.52 Jun 1, 2024
@wenshao wenshao removed the fixed label Jun 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants