One of more powerful features of Jackson is its ability to use arbitrary
constructors for creating POJO instances, by indicating constructor to
use with @JsonCreator annotation. A simple explanation
of this feature can be found from FasterXML wiki; but it only scratches
surface of all the power this annotation exposes. This article will
expand on what can be done with this annotation.
1. What are Creators in Jackson?
"Creator" refers to two kinds of things: constructors, and static
factory methods: both can be used to construct new instances. Term
"creator" is used for convenience, to avoid repeating "constructor or
static factory method".
In some places term "creator method" may be used, although this is not
preferred (since constructors are not regular methods). In case of
static factory methods, method's return type must be same as declaring
classes type, or its subtype.
2. Two kinds of creators: property-based, delegate-based
There are two kinds of creators that one can denote using @JsonCreator:
property- and delegate-based creators:
-
Property-based creators take one or more arguments; all of which MUST
be annotated with @JsonProperty, to specify JSON name used for
property. They can only be used to bind data from JSON Objects; and
each parameter represents one property of the JSON Object; type of
property being used for binding data to be passed as that parameter
when calling creator.
-
Delegate-based creators take just one argument, which is NOT annotated
with @JsonProperty. Type of that property is used by Jackson to bind
the whole JSON value (JSON Object, array or scalar value), to be
passed as value of that one argument.
In addition, sometimes distinction is made between two kinds of
delegate-based creators: those that take scalar value
(int/Integer/long/Long, string or boolean/Boolean) and other delegates.
There is only one important distinction from user perspective: there is
no creator overloading, so only one creator of each type is alllowed.
More on this later on.
3. Property-based creators
Property-based creators are typically used to pass one or more
obligatory parameters into constructor (either directly or via factory
method). If a property is not found from JSON, null is passed instead
(or, in case of primitives, so-called default value; 0 for ints and so
on).
A typical use case could look like this:
public class NonDefaultBean {
private final String name;
private final int age;
private String type;
@JsonCreator
public NonDefaultBean(@JsonProperty("name") String name, @JsonProperty("age") int age)
{
this.name = name;
this.age = age;
}
public void setType(String type) {
this.type = type;
}
}
where two properties are passed via constructor creator; and then a
third one through regular setter. Note that we could have as well used a
static factory method as creator.
One thing to note about these creators is that it is possible to create
a sub-type of class: this allows implementing polymorphic handling
manually if necessary, although with limitation that creator itself must
be in base class. But all other properties can be passed to sub-classes,
as Jackson is smart enough to check type and properties of the actual
instance, not just declared nominal type.
4. Delegate-based creators
Whereas you can use multiple arguments with properties-based creators
(but must explicitly name them), delegate-based creator takes just one
argument. Jackson will then use type of that argument for data-binding,
and bind JSON to that type before calling creator. Creator is then free
to construct instance any way it wants to. A typical usage looks like:
public class ObjectDelegateBean
{
private final String name;
private final int age;
private final String type;
@JsonCreator
public ObjectDelegateBean(Map<String,Object> props)
{
name = (String) props.get("name");
age = (Integer) props.get("age");
type = (String) props.get("type");
}
}
In this case we just manually extract properties from a Map (where JSON
data has been bound to "natural" types; Maps, Lists, String, Numbers and
Booleans). Other common delegate types used are JsonNode (to use JSON
tree as intermediate form), TokenBuffer (stream of exact JsonTokens,
which can be used to create a JsonParser) and basic java.lang.Object
(which would map to natural types mentioned earlier).
But as I mentioned earlier, it is also possible to use variants to
handle scalar types: specifically ints (or Integers), long (or Longs)
and Strings (in future support will also be added for double/Double).
For example:
public class DateBean
{
private Date date;
private DateBean(Date date) {
this.date = date;
}
@JsonCreator
public static DateBean factory(long timestamp) {
Date d = new Date(timestamp);
return new DateBean(d);
}
}
which is bit contrived example, as Jackson can easily bind
java.util.Date directly. However, some JSON formats support overloading,
or try to minimize size; and JSON Strings are sometimes used to bundle
semi-structured data
One point to note about "scalar delegates" is that Jackson allows
overload of creators, as long as there is only one creator type per JSON
type: so you can have only one properties-based creator (or
delegate-based creator that takes something other than match of JSON
scalar type) , but there can be an additional string-delegate creator as
well as an int-delegate creator. Limitation is conceptually simple:
there must only be one applicable delegate to choose from, knowing type
of JSON value being bound -- and this is why delegates that take String,
Integer/int or Long/long are special cases of delegate-based creators.
5. More to know?
In near future (Jackson 1.9) there will be significant improvements in
this area: new things called "value instantiators" will be allowed to
handle feature set similar to that of @JsonCreator; but that need not be
bound to existing Java constructors or factory methods.