【问题标题】:Spring filtering response fields with jackson使用杰克逊的弹簧过滤响应字段
【发布时间】:2021-05-21 12:45:48
【问题描述】:

我正在尝试过滤嵌套对象中的字段:

class Response {

  // These objects themselves can have many fields within
  private final PropA a;
  private final PropB b;

  @JsonCreator
  public Response(PropA a, PropB b) { ... }
}

我想要一个通用的“过滤器助手”来实现上述逻辑。这是我到目前为止所拥有的(遵循与this project类似的方法)

public class FilterHelper {

    private final ObjectMapper objectMapper;

    public FilterHelper(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
        this.objectMapper.addMixIn(Object.class, MyFilterMixin.class);
    }

    @JsonFilter("myfilter")
    public static class MyFilterMixin {
    }

    private static class MyFilter extends SimpleBeanPropertyFilter {
        private final Set<String> properties;

        public MyFilter(Set<String> properties) {
            super();
            this.properties = properties;
        }

        @Override
        public void serializeAsField(final Object pojo, final JsonGenerator jgen, final SerializerProvider provider,
                                     final PropertyWriter writer) throws Exception {

            System.out.println("************************** " + writer.getName());
            if (properties.contains(writer.getName())) {
                writer.serializeAsField(pojo, jgen, provider);
            } else if (!jgen.canOmitFields()) {
                writer.serializeAsOmittedField(pojo, jgen, provider);
            }
        }
    }

    public String filter(T obj, Set<String> fields) {

        FilterProvider filterProvider = new SimpleFilterProvider().addFilter("myfilter", new MyFilter(fields));

        return objectMapper.writer(filterProvider).writeValueAsString(obj);
    }
}

当我使用?fields=one,two 作为查询参数点击此端点时,我希望从打印到控制台的行中看到该顶级Response 对象中的每个字段,如下所示:

******************* a
******************* a1
******************* a2
******************* ..etc
******************* b
******************* b1
******************* b2
******************* ..etc

但我只看到顶级 ab 字段的输出,然后在从端点获取 500 状态代码之前出现错误:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot resolve PropertyFilter with id 'myfilter'; no FilterProvider configured (through reference chain: com.google.common.collect.SingletonImmutableList[0])

值得一提的是,我以某种方式让它工作了,但经过一些我不记得的更改后它被破坏了。

【问题讨论】:

  • filterHelper 实例是如何创建的?
  • 通过 Spring 和 @Component 注入控制器

标签: java json spring jackson json-serialization


【解决方案1】:

除非您需要为不同的字段提供自定义序列化,否则您不应挂钩 serializeAsField,而应覆盖 #include 变体方法:

  • com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter#include(com.fasterxml.jackson.databind.ser.BeanPropertyWriter)
  • com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter#include(com.fasterxml.jackson.databind.ser.PropertyWriter)

如下:

private static class MyFilter extends SimpleBeanPropertyFilter {
    private final Set<String> properties;

    public MyFilter(Set<String> properties) {
        super();
        this.properties = properties;
    }

    @Override
    protected boolean include(BeanPropertyWriter writer) {
        return !this.properties.contains(writer.getName());
    }

    @Override
    protected boolean include(PropertyWriter writer) {
        return !this.properties.contains(writer.getName());
    }
}

甚至还有一个静态工厂提供了一个com.fasterxml.jackson.databind.ser.PropertyFilter 来过滤掉一组特定的字段:

com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter#serializeAllExcept(java.util.Set<java.lang.String>)

额外问题

在过滤器助手级别,您将过滤后的对象序列化为 JSON,然后将其反序列化(使用过滤后的字段)到您作为端点响应返回的对象。

解决方案/替代方案

您可以简单地省略中间步骤,只需使用过滤字段谓词对结果Response 进行消毒并将结果 JSON 作为ResponseEntity 返回:

FilterHelper:

@Component
public class FilterHelper {

    private final ObjectMapper objectMapper;

    @Autowired
    public FilterHelper(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
        this.objectMapper.addMixIn(Object.class, MyFilterMixin.class);
    }

    @JsonFilter("myfilter")
    public static class MyFilterMixin {
    }

    private static class MyFilter extends SimpleBeanPropertyFilter {
        private final Set<String> properties;

        public MyFilter(Set<String> properties) {
            super();
            this.properties = properties;
        }

        @Override
        protected boolean include(BeanPropertyWriter writer) {
            return !this.properties.contains(writer.getName());
        }

        @Override
        protected boolean include(PropertyWriter writer) {
            return !this.properties.contains(writer.getName());
        }
    }

    public String filter(Object dto, Set<String> fields) {
        if (fields == null || fields.isEmpty()) {
            return "";
        }

        FilterProvider filterProvider = new SimpleFilterProvider()
                .addFilter("myfilter", SimpleBeanPropertyFilter.serializeAllExcept(fields));
        try {
            return objectMapper.writer(filterProvider).writeValueAsString(dto);
        } catch (JsonProcessingException e) {
            return "";
        }
    }
}

Controller:

@GetMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(OK)
ReponseEntity<String> someEndpoint(@RequestParam(name = "fields") Set<String> fields) {
  Response response = getResponseFromSomewhere();
  return ResponseEntity.ok(filterHelper.filter(response, fields));
}

【讨论】:

  • 非常感谢。我知道serializeAllExcept,但它不执行嵌套字段过滤,这正是我所追求的。我链接到的那个 github 项目就是这样做的,这就是为什么我尝试做类似的事情。
  • 上述方法是否满足您的需求?否则,您能否修复项目链接(您提供了指向 GitHub 配置文件的链接)。是的,SimpleBeanPropertyFilter#serializeAllExcept 确实支持嵌套字段,你可以自己试试。
  • 也许您的方法在理论上可行,但在我的情况下我仍然遇到 JsonFilter 异常。可能是一个特定的问题。也许serializeAllExcept 确实支持您所说的嵌套,但我在他们的文档或其他任何说明它支持或如何做到这一点的地方都找不到。感谢您的帮助
  • 我不是在谈论理论,我提供了一个工作示例。您仍然可以使用 sn-ps 进行检查。除非我错过了您的观点,或者您的意思是 nested property full qualified property 名称,否则如果没有额外的提示,我将无能为力。
猜你喜欢
  • 1970-01-01
  • 2021-05-28
  • 1970-01-01
  • 1970-01-01
  • 2013-02-10
  • 2017-08-18
  • 1970-01-01
  • 1970-01-01
  • 2015-08-18
相关资源
最近更新 更多