I usually post about Jackson and JSON stuff on Twitter - you can follow me there:

1. Overview

This tutorial is going to illustrate how we can use Jackson to only serialize a field if it meets a specific, custom criteria.

For example, say we only want to serialize an integer value if it’s positive – and we want to skip it entirely if it’s not.

If you want to dig deeper and learn other cool things you can do with the Jackson 2 – head on over to the main Jackson tutorial.

2. Use Jackson Filter to Control the Serialization Process

First, we need to define the filter on our entity, using the @JsonFilter annotation:

@JsonFilter("myFilter")
public class MyDto {
    private int intValue;

    public MyDto() {
        super();
    }

    public int getIntValue() {
        return intValue;
    }

    public void setIntValue(int intValue) {
        this.intValue = intValue;
    }
}

Then, we need to define our custom PropertyFilter:

PropertyFilter theFilter = new SimpleBeanPropertyFilter() {
   @Override
   public void serializeAsField
    (Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer)
     throws Exception {
      if (include(writer)) {
         if (!writer.getName().equals("intValue")) {
            writer.serializeAsField(pojo, jgen, provider);
            return;
         }
         int intValue = ((MyDtoWithFilter) pojo).getIntValue();
         if (intValue >= 0) {
            writer.serializeAsField(pojo, jgen, provider);
         }
      } else if (!jgen.canOmitFields()) { // since 2.3
         writer.serializeAsOmittedField(pojo, jgen, provider);
      }
   }
   @Override
   protected boolean include(BeanPropertyWriter writer) {
      return true;
   }
   @Override
   protected boolean include(PropertyWriter writer) {
      return true;
   }
};

This filter contains the actual logic deciding if the intValue field is going to be serialized or not, based on its value.

Next, we hook this filter into the ObjectMapper and we serialize an entity:

FilterProvider filters = new SimpleFilterProvider().addFilter("myFilter", theFilter);
MyDto dtoObject = new MyDto();
dtoObject.setIntValue(-1);

ObjectMapper mapper = new ObjectMapper();
String dtoAsString = mapper.writer(filters).writeValueAsString(dtoObject);

And finally, we can check that the intValue field is indeed not part of the marshalled JSON output:

assertThat(dtoAsString, not(containsString("intValue")));

3. Skip Objects Conditionally

Now – let’s discuss how to skip objects while serializing based on property value. We will skip all objects where property hidden is true:

3.1. Hidable Classes

First, let’s take a look at our Hidable Interface:

@JsonIgnoreProperties("hidden")
public interface Hidable {
    boolean isHidden();
}

And we have two simple classes implementing this interface Person, Address:

Person Class:

public class Person implements Hidable {
    private String name;
    private Address address;
    private boolean hidden;
}

And Address Class:

public class Address implements Hidable {
    private String city;
    private String country;
    private boolean hidden;
}

Note: We used @JsonIgnoreProperties(“hidden”) to make sure hidden property itself is not included in JSON

3.2. Custom Serializer

Next – here is our custom serializer:

public class HidableSerializer extends JsonSerializer<Hidable> {

    private JsonSerializer<Object> defaultSerializer;

    public HidableSerializer(JsonSerializer<Object> serializer) {
        defaultSerializer = serializer;
    }

    @Override
    public void serialize(Hidable value, JsonGenerator jgen, SerializerProvider provider)
      throws IOException, JsonProcessingException {
        if (value.isHidden())
            return;
        defaultSerializer.serialize(value, jgen, provider);
    }

    @Override
    public boolean isEmpty(SerializerProvider provider, Hidable value) {
        return (value == null || value.isHidden());
    }
}

Note that:

  • When the object will not be skipped, we delegate the serialization to the default injected serializer.
  • We overridden the method isEmpty() – to make sure that in case of Hidable object is a property, property name is also excluded from JSON.

3.3. Using BeanSerializerModifier

Finally, we will need to use BeanSerializerModifier to inject default serializer in our custom HidableSerializer – as follows:

ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_EMPTY);
mapper.registerModule(new SimpleModule() {
    @Override
    public void setupModule(SetupContext context) {
        super.setupModule(context);
        context.addBeanSerializerModifier(new BeanSerializerModifier() {
            @Override
            public JsonSerializer<?> modifySerializer(
              SerializationConfig config, BeanDescription desc, JsonSerializer<?> serializer) {
                if (Hidable.class.isAssignableFrom(desc.getBeanClass())) {
                    return new HidableSerializer((JsonSerializer<Object>) serializer);
                }
                return serializer;
            }
        });
    }
});

3.4. Sample Output

Here is a simple serialization example:

Address ad1 = new Address("tokyo", "jp", true);
Address ad2 = new Address("london", "uk", false);
Address ad3 = new Address("ny", "usa", false);
Person p1 = new Person("john", ad1, false);
Person p2 = new Person("tom", ad2, true);
Person p3 = new Person("adam", ad3, false);

System.out.println(mapper.writeValueAsString(Arrays.asList(p1, p2, p3)));

And the output is:

[
    {
        "name":"john"
    },
    {
        "name":"adam",
        "address":{
            "city":"ny",
            "country":"usa"
        }
    }
]

3.5. Test

Finally – here is few test cases:

First case, nothing is hidden:

@Test
public void whenNotHidden_thenCorrect() throws JsonProcessingException {
    Address ad = new Address("ny", "usa", false);
    Person person = new Person("john", ad, false);
    String result = mapper.writeValueAsString(person);

    assertTrue(result.contains("name"));
    assertTrue(result.contains("john"));
    assertTrue(result.contains("address"));
    assertTrue(result.contains("usa"));
}

Next, only address is hidden:

@Test
public void whenAddressHidden_thenCorrect() throws JsonProcessingException {
    Address ad = new Address("ny", "usa", true);
    Person person = new Person("john", ad, false);
    String result = mapper.writeValueAsString(person);

    assertTrue(result.contains("name"));
    assertTrue(result.contains("john"));
    assertFalse(result.contains("address"));
    assertFalse(result.contains("usa"));
}

Now, entire person is hidden:

@Test
public void whenAllHidden_thenCorrect() throws JsonProcessingException {
    Address ad = new Address("ny", "usa", false);
    Person person = new Person("john", ad, true);
    String result = mapper.writeValueAsString(person);

    assertTrue(result.length() == 0);
}

4. Conclusion

This type of advanced filtering is incredibly powerful and allows very flexible customization of the json when serializing complex objects with Jackson.

A more flexible but also more complex alternative would be using a fully custom serializer to control the JSON output – so if this solution isn’t flexible enough, it may be worth looking into that.

The implementation of all these examples and code snippets can be found in my github project – this is an Eclipse based project, so it should be easy to import and run as it is.

I usually post about Jackson and JSON stuff on Twitter - you should follow me there:


  • Abhishek

    Could you please help me to know how to integrate this with Spring REST?

    • To configure this in a Spring REST project – you’ll need to configure the ObjectMapper (as described here) – and the way to get to the ObjectMapper is described here.
      Hope that helps – if, after going through this process, you’re having any issues – follow up on this comment and I’ll give it a go.
      Cheers.
      Eugen.

  • Murtaza Kanchwala

    Nice post! But what If I want to define a whole new custom json response?

    • You could of course define your own custom serializer and have full control over the JSON you’re outputting. I am covering the custom serializer route in some of the other Jackson articles, but I’m going to mention it here as an alternative as well – thanks for the suggestion. Cheers,
      Eugen.

  • Valentin

    Do you know how to use such a filter to hide/show field based on SPring security roles ? Thank you

    • That’s a really interesting scenario – and one that I solved in a few different ways in the past.
      If you really want to go for the complex solution (which is what you’re describing here) – you’re going to have to built it out manually.
      Which is fine, just complex – so do think about some simpler alternatives first. Cheers,
      Eugen.

    • Sunny

      Please checkout the role based entity filtering feature provided by Jersey. Based on the logged in user we can filter the json object. https://jersey.java.net/documentation/latest/entity-filtering.html#ef.security.annotations Hope it helps

  • robertmircea

    Suppose I have a REST API which allows the caller to specify which fields from JSON response he wants in reply instead of the full representation.

    For example: calling http://api/user/1 would return:

    {
    id: 1,
    username: “neilford”,
    email: “neil@somewhere.com”,
    name: “Neil Ford”
    }

    but if the caller wants less information:

    http://api/user/1?fields=username,email

    {
    username: “neilford”,
    email: “neil@somewhere.com”
    }

    Do you have any hints on how to implement this kind of dynamic fields serialization of JSON response based on caller’s choice?

    • That’s an advanced and very interesting usecase – providing a field plan/fetch plan from the client side. The only way is to roll your own implementation – I’m not aware of anything available out of the box.
      It’s actually in the Content Calendar of the site, so I’m going to publish an implementation at some point soon.
      Cheers,
      Eugen.

      • robertmircea

        Cheers! Did you have the chance to complete this implementation? I am still very much interested in the subject.

        • I have – very recently, just didn’t have time to write about it yet. Have a look in this repo here. Hope it helps. Cheers,
          Eugen.

          • robertmircea

            I can’t seem to find the use case I’ve described implemented in your repo: dynamically selecting fields of resources based on request query parameters to serialize in controller’s response. The idea is that API consumer specifies whatever fields from JSON reply resource wants from the server.

          • Here you go.
            Hope that helps. Cheers,
            Eugen.

  • chris marx

    Is there any way to make this work for nested collections?

    • Hey Chris – that’s an interesting question. Generally I would say yes, but I’ll have to look at an actual test to see exactly what you’re trying to do. Feel free to write a test and open a PR for the code associated to this article and I’d be happy to have a look. Cheers,
      Eugen.

  • James

    3.2 — isn’t overriding the serialize method redundant? It will never be called for a hideable anyway. Seems like it should just be a call to super or at least skip the hidden check. Thanks for the article.

    • Hey James,
      Have a look at the jackson module over on Github and try to run the associated tests (JacksonDynamicIgnoreTest). You’ll notice that – if you have a breakpoint right on that check, it will actually get triggered.
      And of course that way you can also see the full stack and see how the method gets to be called.
      Hope that helps to clear things up. Cheers,
      Eugen.