Jackson Tips: filtering with @JsonInclude
Tags: Java, Json, Jackson

Jackson Tips: filtering with @JsonInclude

When serializing content as JSON (or another format Jackson supports), Jackson by default just writes out all property values as they are.

Many users want to change this behavior so that certain property values should be “filtered out”, not written as output. For example so that only properties with non- null values should not be written — this can help reduce message and sparse data record sizes.

Jackson offers multiple ways to limit writing of output by various “filtering” mechanisms: for an overview read “Every day Jackson usage, part 3: Filtering properties” (written 10 years ago butcovers most choices).

Options range from mostly static definitions (JSON Views, @JsonInclude via annotations) to fully dynamic ones (JSON Filters, BeanSerializerModifier ).

This article focuses on use of @JsonInclude both via annotations and “config overrides” mechanism.

@JsonInclude: basics

When @JsonInclude is used to specify whether value of detected property should be written out or not:

Only properties of POJOs are processed (but values themselves can be of any type) — JsonNode values (like ObjectNode ) are not filtered, nor are elements of Collection s or arrays. Determination of inclusion/exclusion is based on Java value, and not on output (JSON etc) serialization — this can be sometimes confusing (“why is my value type, serialized as “{ }” still written out despite ‘NON_EMPTY’ inclusion criteria used) You can only exclude output; cannot change what is output

The actual inclusion logic to apply is specified using one values JsonInclude.Include enum values:

ALWAYS : Global default which means that nothing is filtered out (all values included)

: Global default which means that nothing is filtered out (all values included) NON_NULL : All values except for Java null s are included

: All values except for Java s are included NON_ABSENT : All values except for Java null s and “absent” values for referential types (such as Java 8 Optional.empty() and AtomicReference with null value) are included

: All values except for Java s and “absent” values for referential types (such as Java 8 and with value) are included NON_EMPTY : Same as NON_ABSENT but also excludes empty Collection s, Map s and arrays, as well as String s with length of 0

: Same as but also excludes empty s, s and arrays, as well as s with length of 0 NON_DEFAULT : If property has “default” value (one assigned to property when containing POJO is constructed with default [no arguments] constructed — or is “empty” as per above definition), will be excluded. When applied as global or type default, will instead consider “default” value of relevant type, such as 0 for Integer . Due to this inconsistency, you want to be careful if using this criteria.

: If property has “default” value (one assigned to property when containing POJO is constructed with default [no arguments] constructed — or is “empty” as per above definition), will be excluded. When applied as global or type default, will instead consider “default” value of relevant type, such as for . Due to this inconsistency, you want to be careful if using this criteria. CUSTOM : Exclusion criteria specified using addition “filter” object, type of which is specified using valueFilter and/or contentFilter property of @JsonInclude . Explained in detail later on.

: Exclusion criteria specified using addition “filter” object, type of which is specified using and/or property of . Explained in detail later on. USE_DEFAULTS : pseudo-value used to indicate “use whatever [global]/[type] defaults are”, needed as default setting for @JsonInclude — you are unlikely to ever see it used (nor should usually use it)

And finally, one of above settings is applied for property in question. Applicability can be defined at any of 4 (!) levels, in increasing precedence order:

Global default: ObjectMapper.setDefaultPropertyInclusion(...) — used if no other settings apply Per-class @JsonInclude annotation (specifies default inclusion for Class being annotated (overrides global default) Per-type inclusion default: ObjectMapper.configOverride(MyValue.class).setInclude(...) which similarly specifies inclusion for type specified (but overrides global default and possible per-class annotation) Per-property inclusion override: @JsonInclude on property accessor (field, setter/getter, constructor argument) — overrides defaults from other 3 levels

This means that you have a few options in specifying logic you want. For example, you can see settings like this:

ObjectMapper mapper = JsonMapper.builder()

// Global default: exclude null values, but keep nulls in Maps

.defaultPropertyInclusion(JsonInclude.Value.construct(

JsonInclude.Include.NON_NULL, JsonInclude.Include.ALWAYS)

.build();

mapper.configOverride(MyNullKeepingClass.class)

.setInclude(...); // keep NULLs for this type @JsonInclude(JsonInclude.ALWAYS) // keep most fields, even if null

public class OtherContainerValue {

// but don't bother outputting empty Lists

@JsonInclude(JsonInclude.NON_EMPTY)

public List<String> ids;

// a few other fields:

...

}

in which you can specify general defaults (do not write properties with Java null value) but have targeted overrides for some specific value types and/or properties.

@JsonInclude detour: Value vs Content

Before looking at custom inclusion criteria, there is one more complication to consider: distinction between main “Value” of property and related “Content”s of some types (container types, Collection s and especially Map s and “referential types” like Java 8 Optional ).

For simple scalar types, there is nothing more than Value itself to consider, but for case of Map and Optional there is the container itself (which may be null or empty/non-empty) as well as contents — values contained in the container.

(NOTE: while Collection s and arrays are container types as well, @JsonInclude can NOT used to filter out contents of such types currently, as of Jackson 2.12)

Also note that POJOs are not considered structured types in this sense: they do not have “content” but properties, and rules for those properties follow basic value rules.

You can specify different filtering (exclusion) criteria for Map value itself:

Include only NON_NULL or NON_EMPTY values: use for Map valued property

but you can ALSO specify Content (element) filtering criteria for elements:

Include only NON_NULL (etc) values that Map contains

This means that you can distinguish between cases of “Map being null” from “a value within Map being null”. This does also allow you to filter out not only Map s with no entries (empty Map) but optionally Map s with only entries with null values: in latter cases you would use NON_EMPTY as “value” criteria and NON_NULL as “content” criteria, like so:

public class MyStuff {

@JsonInclude(value = JsonInclude.Include.NON_EMPTY,

content = JsonInclude.INCLUDE_NON_NULL)

public Map<String, String> properties;

}

@JsonInclude: custom criteria

Although a set of filtering options available has been extended over time, it is not really possible to cover all things users might want to use for exclusion.

But one specific option — JsonInclude.Include.CUSTOM — actually allows use of fully custom criteria. You would typically use it on properties (although technically you can try using it at any of 4 levels), as follows:

public class MyValue {

@JsonInclude(value = JsonInclude.Include.CUSTOM,

valueFilter = MyStringFilter.class)

public String name;

} // Filter that excludes 'null' and "NO VALUE" Strings

static class MyStringFilter {

// Return true if filtering out (excluding), false to include

@Override

public boolean equals(Object o) {

// only ever called with String value

String other = (String) o;

return (other == null) || "NO VALUE".equals(other);

}

} // Or, if you wanted this to apply to ALL String valued-properties:

ObjectMapper mapper = JsonMapper.builder()

.configOverride(String.class)

.setInclude(JsonInclude.Value.empty()

.withValueInclusion(JsonInclude.Include.CUSTOM)

.withValueFilter(MyStringFilter.class)

).build();

In this case, String value (of annotated property, or, if configured globally, any String -valued property) would only be written if it is

Not null and

and Not exact String “NO VALUE”

As you can see, the instantiated filter Object ‘s equals() method will be called with the value in question and if value returned is true , value is EXCLUDED (not written out). So make the method match anything you want to filter out and return false for anything that should be written out normally.