Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filtering fields and sub-fields in Jackson

For some JSON:

{
  "id":123,
  "name":"Test",
  "sub_object":{
    "sub_field_1":1,
    "sub_field_2":2,
    "sub_field_array":[
      {
        "array_field_1":true,
        "array_field_2":false
      },
      {
        "array_field_1":false,
        "array_field_2":true
      }
    ],
    "sub_sub_object":{
      "field_1":"me",
      "field_2":"myself",
      "field_3":"i",
    }
  }
}

I want to apply a tree-like list of field names. This could probably be expressed in JSONPath:

root
  |-id
  |-sub_object
    |-sub_field_2
    |-sub_field_array
      |-array_field_1
    |-sub_sub_object

Then I should get back something like:

{
  "id":123,
  "sub_object":{
    "sub_field_2":2,
    "sub_field_array":[
      {
        "array_field_1":true
      },
      {
        "array_field_1":false
      }
    ],
    "sub_sub_object":{
      "field_1":"me",
      "field_2":"myself",
      "field_3":"i",
    }
  }
}

The idea is that, for some field hierarchy, I want to limit the fields that are output.

I am doing this through a library that has annotated its objects' fields, but I cannot modify the library. It wouldn't really matter if I could because the hierarchy will be on a per-serialization basis. Currently, I am passing the objects into the JsonGenerator's writeObject method, but that returns everything.

Some sub-objects may share field names, so it is not as simple as creating a SimpleBeanPropertyFilter to serialize only a set of names.

Thank you in advance,

John

like image 733
user1236874 Avatar asked Nov 12 '22 20:11

user1236874


1 Answers

You can write a custom property filter which would take into account the declaring class of the serialised properties.

You should extend the SimpleBeanPropertyFilter and override the include(PropertyWriter writer) method. If the given writer parameter is an instance of BeanPropertyWriter class, you can extract the information about the property origin and apply your custom the filtering logic.

Here is an example of a filter which stores the exclude information in a map of classes and their property names:

public class JacksonHierarchyFilter {
    @JsonFilter("filter")
    public static class A {
        public final String field1;

        public A(final String field1) {this.field1 = field1;}
    }

    @JsonFilter("filter")
    public static class B {
        public final String field1;
        public final List<A> list;

        public B(final String field1, final List<A> list) {
            this.field1 = field1;
            this.list = list;
        }
    }
    @JsonFilter("filter")
    public static class Foo {
        public final String field1;
        public final List<B> field2;

        public Foo(final String field1, final List<B> field2) {
            this.field1 = field1;
            this.field2 = field2;
        }
    }

    public static class MyFilter extends SimpleBeanPropertyFilter {
        private final Map<Class<?>, Set<String>> excludePropMap;

        public MyFilter(final Map<Class<?>, Set<String>> excludePropMap) {
            this.excludePropMap = excludePropMap;
        }

        @Override
        protected boolean include(final BeanPropertyWriter writer) {
            return false;
        }

        @Override
        protected boolean include(final PropertyWriter writer) {
            if (writer instanceof BeanPropertyWriter) {
                final Class<?> cls = ((BeanPropertyWriter) writer).getMember().getDeclaringClass();
                final Set<String> excludePropSet = excludePropMap.get(cls);
                return excludePropSet == null || !excludePropSet.contains(writer.getName());
            }
            return true;
        }
    }

    public static void main(String[] args) throws JsonProcessingException {
        final B b = new B("B1", Arrays.asList(new A("A1"), new A("A2")));
        final Foo foo = new Foo("foo", Arrays.asList(b));
        final ObjectMapper mapper = new ObjectMapper();
        final SimpleFilterProvider filters = new SimpleFilterProvider();
        final Map<Class<?>, Set<String>> excludePropMap =
                Collections.<Class<?>, Set<String>>singletonMap(
                        B.class,
                        Collections.singleton("field1"));
        filters.addFilter("filter", new MyFilter(excludePropMap));
        mapper.setFilters(filters);
        final ObjectWriter objectWriter = mapper.writerWithDefaultPrettyPrinter();
        System.out.println(objectWriter.writeValueAsString(foo));
    }

}

Output:

{
  "field1" : "foo",
  "field2" : [ {
    "list" : [ {
      "field1" : "A1"
    }, {
      "field1" : "A2"
    } ]
  } ]
}
like image 89
Alexey Gavrilov Avatar answered Dec 22 '22 22:12

Alexey Gavrilov