Moo-able Type for Cowtowncoder.com

Tuesday, October 11, 2011

Jackson 1.9 new feature overview

Jackson 1.9 was just released. As usual, it can downloaded from the Download
page, and detailed release information can be found from 1.9
release page.

Let's have a look into contents of this release.

1. Overview

One of focus areas on this release was once again to tackle oldest
significant issues and improvement ideas; and two of major new features
are long-standing issues (ability to inline/unwrap JSON values; unify
annotation handling for getters/setters/fields). Another big goal was to
improve ergonomics: to simplify configuration, shorten commonly used
usage patterns and so on. And finally there was also intent to try to
"2.0 proof" things, by trying to figure out things that need to be
deprecated to allow removal of obsolete methods as well as indicate
cases where improved functionality is available.

2. Major features

(note: classification of features into major, medium and minor
categories is not exact science, and different users might consider
different things more important than others -- here we simply use
categorization that the release page uses)

Major features included in 1.9 are:

Allow inlining/unwrapping of child objects using @JsonUnwrapped

Rewrite property introspection part of framework to combine
getter/setter/field annotations

Allow injection of values during deserialization

Support for 'external type id' by adding
@JsonTypeInfo.As.EXTERNAL_PROPERTY

Another long-standing issue has been that of isolation between
annotations used by getters, setters and fields. Basically annotation
added to a getter was only ever used for serialization, and would never
have any effect on deserialization; similarly setter never affected
deserialization. While this is not a problem for many annotation use
cases, it would make following use case work quite different from what
users intuitively expect:

which would actually lead to there being two separate properties:
"width" that is written out during serialization; and "w" that is
expected to be received when deserializing. Many users would intuitively
expect annotation to be "shared" between two parts of logically related
accessors. Same issue also affects annotations like @JsonIgnore and
@JsonTypeInfo, requiring use of seemingly redundant annotations.

Jackson 1.9 solves this by adding new internal representation of logical
property, and merging resulting annotations using expected priorities
(meaning that annotations on a getter have precedence over setter when
serializing, and vice versa).

There are also other more subtle changes, related to these changes. For
example, class like:

can now be deserialized succesfully, even without field "value" being
visible or annotated: since it is joined with getter ("getValue()"), and
getter is explicitly annotated, field is included as the accessor to use
for assigning value for the property.

The last important benefit of this feature is that now handling of
Jackson and JAXB annotations is much more similar, which should make
JAXB annotations works better as a result (code was simplified
significantly) -- this because JAXB had always considered annotations to
be shared in this way.

2.3 Value Injection for Deserialization

Value injection here means ability to insert ("inject") values into
POJOs outside of general data binding: that is, values that do not come
from JSON input. Instead, values to inject are specified during
configuration of ObjectMapper or ObjectReader used for data binding.

Why is this needed? Some Java types require additional context
information to be able to construct POJO instances, for example. And in
other cases, you may want to pre-populate values of some fields; and
while there are other mechanims (for example, you can pass an existing
POJO instance for "updateValue()") method) they are quite limited.

Only two things are needed for value injection:

Means to indicate properties for which values are to be injected, and

Definition of values to inject

Default mechanism is to handle first part by using new annotation, @JacksonInject,
so that we could have:

and second part is handled by allowing configuration of ObjectMapper or
ObjectWriter instance with InjectableValues, object that can find
values to inject given value id. Value ids can be specified as either
Strings, or as Classes; if Class is used, Class.getName() is used to get
actual String id to use. For above POJO, we could handle deserialization
as follows:

For more on this feature, check out FasterXML Wiki's entry on Value
Injection.

2.4 External Type Id

Jackson has had support for full polymorphic type handling since 1.5,
allowing configuration of both type identifier in use (usually either a
class name, or logical type name) and type inclusion mechanism (as
property, as wrapper array, as single-element wrapper object).This
covers wide range of usage scenarios, but there is one inclusion
mechanism that is sometimes used but could not be supported by Jackson:
that of using "external type identifier". This style of type inclusion
is used by some data formats, most notably geoJSON.

One thing to note here is that this inclusion mechanism should only be
used with properties; annotating classes with @JsonTypeInfo that
indicates external type identifiers can cause conflicts.

2.5 Value instantiators

And last but not least, 1.9 also allows much more control over mechanism
used to create actual POJO value instances. While Jackson 1.2 added
support for @JsonCreator annotation, there has not been a way to add
custom creator objects.

Simple no-arguments construction (ValueInstantiator.createUsingDefault()):
used if the other construction mechanisms are not available: consumes
no JSON properties.

Delegate-based construction (ValueInstantiator.createUsingDelegate(Object)):
similar to annotating a single-argument constructor or factory method
with @JsonCreator, but NOT specifying argument name with
@JsonProperty. If specified (i.e. value instantiator indicates it
supports this), JSON value for property is first bound into
intermediate (delegate) type, and then this value is passed to
delegate creator method. Jackson mapper will handle all the details of
initial binding, passing delegate object as the argument.

Property-based construction (ValueInstantiator.createFromObjectWith(Object[]
args)): similar to using @JsonCreator with arguments that all have
@JsonProperty annotation to specify JSON property name to bind.

It is worth noting that order in which availability of different modes
is checked is reverse of above: first a check is made to see if
property-based method is available; if not, then delegate-based, and
finally default construction.

Since this is possibly the most complicated new feature, I will need to
defer a full example to another blog post. But let's consider a very
simple ValueInstantiator implementation that just supports the default
(no-argument) instantiation:

"Mini core": as name suggests, there is now a new jar
(jackson-mini-1.9.0.jar) that is about 40% smaller than the default one
-- about 136kB or so. Size reduction is achieved by leaving out text
files (LICENSE), as well as annotations, but otherwise functionality is
equivalent to standard core package, i.e. supports streaming API
(JsonParser/JsonGenerator, JsonFactory).

DeserializationConfig.Feature.UNWRAP_ROOT_VALUE is counterpart to SerializationConfig.Feature.WRAP_ROOT_VALUE;
and there is also now a new annotation -- @JsonRootName -- that
can be used to use custom wrapper name instead of the simple class name.
This is useful with interoperability, as some frameworks insist on
adding such wrappers.

One of few improvements to JAX-RS provider is that now you can add
@JsonView annotation to JAX-RS resource methods, and if one is found, it
will be set as the active Serialization View during serialization of the
result value.

One nice ergonomic improvement is the ability to use much more compact
configuration methods for changing default introspection visibility
levels.For example, you can use:

An improvement to naming strategy support is inclusion of one "standard"
naming strategy -- CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES -- which
converts between standard Java Bean names (that setters and getters
use), and C-style names (like used by Twitter). You can enable this
converter by:

As to sub-typing, you can now use new @JsonTypeInfo property
defaultSubType to indicate, as name suggests, default sub-type to use in
case where type name was missing or could not be resolved: use it like:

And finally, one improvement to Json Filter functionality is ability to
specify that it is ok to use a filter id that does not refer to an
actual filter (i.e. can not be resolved by the currently configured
filter provider) -- use 'SimpleFilterProvider.setFailOnUnknownId(false)'
to make this the default behavior. Missing filter is then assumed to
mean "no filtering", that is, serialization is handled as if no filter
was specified.