I just announced the new Spring 5 modules in REST With Spring:

>> CHECK OUT THE COURSE

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 just announced the new Spring 5 modules in REST With Spring:

>> CHECK OUT THE LESSONS

Sort by:   newest | oldest | most voted
Abhishek
Guest

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

Eugen Paraschiv
Guest

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.

Michael Koegel
Guest

Great post. How can I omit a field that is not a simple property, but a serializable Object itself. In my current version based on your example this produces “objectName: {}”, but I’d like to omit it completely.

Here’s my question with example code on StackOverflow: http://stackoverflow.com/questions/31121093/jackson-custom-serializer-with-conditionally-hidden-members-produces-invalid-jso/31121552#31121552

Murtaza Kanchwala
Guest

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

Eugen Paraschiv
Guest

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
Guest

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

Eugen Paraschiv
Guest

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
Guest

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
Guest

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: “[email protected]”,
name: “Neil Ford”
}

but if the caller wants less information:

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

{
username: “neilford”,
email: “[email protected]
}

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

Eugen Paraschiv
Guest

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
Guest

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

Eugen Paraschiv
Guest

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
Guest

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.

Eugen Paraschiv
Guest

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

wpDiscuz