Using Jackson annotations with Jackson-jr

(to rename and remove properties)

Something that I briefly covered earlier (in Jackson 2.11 features) is the new jackson-jr extension called jackson-jr-annotation-support. But as I have not gotten much feedback since that release, maybe it is time to re-review this additional functionality.
(note: if you are not familiar with Jackson-jr library itself, you may want to read “Jackson-jr for ‘casual JSON’” first before continuing)

Introduction of general “extension” mechanism for jackson-jr — not unlike full Jackson’s “modules” — coincided with the addition of first such extension, “jackson-jr-annotation-support”, which offers optional support for some of basic Jackson annotations for basic detection (and exclusion) of properties; renaming, aliasing, reordering (for output).

To use this extensions you will need to add a dependency in your build file; with Maven you would add:

<dependency>
<groupId>com.fasterxml.jackson.jr</groupId>
<artifactId>jackson-jr-annotation-support</artifactId>
<version>2.12.0</version>
</dependency>

and then you will need to register it with the JSON instance you use:

JSON j = JSON.builder().register(JacksonAnnotationExtension.std)
// add other configuration, if any
.build();

after doing this, support would be enabled for following annotations:

  • @JsonProperty for basic inclusion, renaming
  • @JsonPropertyOrder for defining specific order of properties when writing
  • @JsonIgnore / @JsonIgnoreProperties for ignoring specified visible properties
  • @JsonAlias for specifying alternate names to accept when reading
  • @JsonAutoDetect for changing default visibility rules for methods (can ignore public getters/setters and force use of annotations) and fields (can make non-public fields visible)

So let’s have a look at some common usage patterns.

Perhaps the most common use case for annotations is that of renaming properties after auto-detection. Consider, for example, following case:

public class NameSimple {
@JsonProperty("firstName")
@JsonAlias({ "fn" })
protected String _first;

@JsonProperty("lastName")
protected String _last;

protected NameSimple() { }
public NameSimple(String f, String l) {
_first = f;
_last = l;
}
}

in this case we would both indicate use of respective fields to access 2 logical properties (instead of having to add getters and setters) and also rename them, so that compatible JSON would be like

{ "firstName":"Bob", "lastName":"Burger" }

or, considering additional alias we also specified, possibly:

{ "fn":"Bob", "lastName":"Burger" }

(but always serialized with “firstName”: alias only considered during deserialization)

As with regular Jackson, renaming only needs to be done by annotating one of accessors, for example:

public class ValueHolder {
// not needed for setting or getting, name does not matter
private int v;
@JsonProperty("value")
public int getVal() { return v; }
// no need to repeat, gets renamed as well
public void setVal(int v) { this.v = v; }
}

Another common use for annotations is to ignore property otherwise implied by existence of a getter or a setter. For example, consider this class, where by default we would see metadata property, but that is not meant to be serialized:

public class Point
public int x;
public int y;

protected XY() { }
public XY(int x, int y) {
this.x = x;
this.y = y;
}
// diagnostics for logging, not wanted for serialization
@JsonIgnore
public Metadata getMetadata() {
return calculateMetadata();
}
}

alternative we may sometimes want to use an alternative, @JsonIgnoreProperties, especially if ignoring properties from the superclass:

// parent class has "getMetadata()", let's ignore:
@JsonIgnoreProperties({ "metadata" })
public class Point extends ValueWithMetadata
{
public int x, y;
}

By default, Jackson-jr serializes properties in alphabetic order, but sometimes you may want to use different ordering. If so,

@JsonPropertyOrder({ "x", "y", "width", "height" })
public class Rectangle {
public int x, y;
public int width, height;
}

(note: there is no way to try to force “declaration order” — JDK does not guarantee such an ordering is available via Reflection, even for a single class, and trying to combine ordering across fields and methods, super/subclasses would make this futile exercise even if it di)

By default, Jackson-jr auto-detects properties based on finding:

  • public getters and setters
  • public fields (if field-detection enabled)
  • public “is-getters” (like boolean isEnabled() — if is-getter detection enabled)

but sometimes you might want to either prevent auto-detection altogether for certain kinds of accessors, or alternatively auto-detect accessors with lower visibility. You can use Jackson class annotation @JsonAutoDetect for this purpose. Declaration like this, for example:

@JsonAutoDetect(
setterVisibility = JsonAutoDetect.Visibility.ANY,
getterVisibility = JsonAutoDetect.Visibility.PROTECTED_AND_PUBLIC,
fieldVisibility = JsonAutoDetect.Visibility.NONE
)
public class MyValue {
}

would:

  1. Auto-detect setter methods of any visibility type (even ones declared private)
  2. Auto-detect public, protected and “package protected” getter methors (not just public)
  3. Not auto-detect any fields, no matter what visibility (not even public)

this would reduce the need for per-accessor annotations or having to change accessor visibility levels. Auto-detection may still be overridden by any explicit annotations like @JsonProperty (to include, regardless of visibility) or @JsonIgnore (ignore regardless of visibility).

In addition to per-class annotation, you may also override the default visibility used by Jackson-jr: this is done when building extension itself, before registering it.
For example:

// start with default visibility configuration
JsonAutoDetect.Visibility vis =
JacksonAnnotationExtension.DEFAULT_VISIBILITY;
// change field-visibility:
vis = vis.withFieldVisibility(
JsonAutoDetect.Visibility.PROTECTED_AND_PUBLIC));
// and then build the JSON instance with extension
final JSON aj = JSON.builder()
.register(JacksonAnnotationExtension.builder()
.withVisibility(vis)
.build())
.build();

would use defaults otherwise, but change field auto-detection to allow detection of protected (and “package protected”) fields in addition to public ones. These defaults may be overridden by per-class @JsonAutoDetect settings, which in turn may be overridden by per-accessor annotations.

Aside from only supporting a small subset of all Jackson annotations, there are some limitations regarding annotations that are supported:

  • No mix-in annotation support
  • Class annotations: only super-class annotations are inherited, super-interface annotations are not
  • Accessor annotations: no “accessor inheritance” (fields, methods) — when overriding methods, annotations from accessor in base class will not be found
  • Some annotations are only supported for classes, but not on accessors (@JsonIgnoreProperties)
  • Only renaming aspect of @JsonProperty is supported

Although there is no plan to support all or even most Jackson annotations (if you want that, consider using “full” Jackson), support may be gradually extended in some areas based on feedback.
Some possible areas of improvement are:

  • @JsonValue
  • Consider supporting mix-in annotations
  • Maybe a subset of @JsonCreator functionality (property-based, explicit) should be supported
  • Naming strategies? (existing @JsonNaming cannot be used, unfortunately, as it is part of jackson-databind , not jackson-annotations)

If you have specific extension ideas, wishes, requests, feel free to file an RFE at Jackson-jr Issue Tracker.

Written by

Open Source developer, most known for Jackson data processor (nee “JSON library”), author of many, many other OSS libraries for Java, from ClassMate to Woodstox

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store