0

I am trying to filter fields in a nested object:

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) { ... }
}

I'd like a generic 'filter helper' to achieve the above logic. Here is what I have so far (following a similar approach as 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);
    }
}

When I hit this endpoint with ?fields=one,two as query parameter I expect to see from a line printed to console for every field within that top level Response object as follows:

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

but I am only seeing output for the top level a and b fields followed by an error before getting a 500 status code from the endpoint:

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

It is worth mentioning that I had this working somehow, but it was broken after some changes I don't recall.

theartv
  • 35
  • 7

1 Answers1

2

Unless you need to provide custom serialization for different fields, you should not be hooking the serializeAsField and instead you should be overriding the #include variant methods:

  • 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)

as follows:

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());
    }
}

There is even a static factory providing a com.fasterxml.jackson.databind.ser.PropertyFilter that filters out a specific set of fields:

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

Extra issue

At the filter helper level, you are serializing the filtered object to JSON then deserializing it back (with filtered fields) to an object that you are handing back as the endpoint response.

Solution / Alternative

You can simply omit the intermediary step by just sterilizing the result Response with the filter fields predicate and returning the result JSON as 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));
}
tmarwen
  • 15,750
  • 5
  • 43
  • 62
  • Great thanks. I was aware of `serializeAllExcept` but it doesn't do nested field filtering which is what I'm after. That github project I linked to does this which is why I tried to do similar to that. – theartv May 21 '21 at 16:44
  • Do the mentioned approach satisfies your needs? Otherwise could you fix the project link (you provided a link to the GitHub profile). And yes, `SimpleBeanPropertyFilter#serializeAllExcept` do support nested fields, you can try it by yourself. – tmarwen May 21 '21 at 17:17
  • Perhaps your approach works in theory but I'm still having the JsonFilter exception in my case. Probably a specific issue. Perhaps `serializeAllExcept` does support nested as you say but I cannot find in their documentation or anywhere else that says it does or how to do it. Thanks for the help – theartv May 22 '21 at 11:47
  • I am not talking theory, I provided a working example. You can still check by using the snippets. Unless I am missing your point or you mean by *nested property* the *full qualified property* name, I cannot help any further without extra hints. – tmarwen May 22 '21 at 16:36