Jackson Annotations
Jakob Jenkov |
The Jackson JSON toolkit contains a set of Java annotations which you can use to influence how JSON is read into objects, or what JSON is generated from the objects. This Jackson annotation tutorial explains how to use Jackson's annotations.
If you are unfamiliar with Java annotations, read my Java annotation tutorial before reading this tutorial. It will help you understand how Java annotations work.
Read + Write Annotations
Jackson contains a set of annotations that affect both the reading of Java objects from JSON, as well as the writing of Java objects into JSON. I refer to these annotations as "read + write annotations". The following sections explain Jackson's read + write annotations in more detail.
@JsonIgnore
The Jackson annotation @JsonIgnore
is used to tell Jackson to ignore a certain property (field)
of a Java object. The property is ignored both when reading JSON into Java objects, and when writing Java
objects into JSON. Here is an example class that uses the @JsonIgnore
annotation:
import com.fasterxml.jackson.annotation.JsonIgnore; public class PersonIgnore { @JsonIgnore public long personId = 0; public String name = null; }
In the above class the property personId
will not be read from JSON or written to JSON.
@JsonIgnoreProperties
The @JsonIgnoreProperties
Jackson annotation is used to specify a list of properties of a class
to ignore. The @JsonIgnoreProperties
annotation is placed above the class declaration instead of
above the individual properties (fields) to ignore. Here is an example showing how to use the
@JsonIgnoreProperties
annotation:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @JsonIgnoreProperties({"firstName", "lastName"}) public class PersonIgnoreProperties { public long personId = 0; public String firstName = null; public String lastName = null; }
In this example both the properties firstName
and lastName
will be ignored because
their names are listed inside the @JsonIgnoreProperties
annotation declaration above the class
declaration.
@JsonIgnoreType
The @JsonIgnoreType
Jackson annotation is used to mark a whole type (class) to be ignored everywhere
that type is used. Here is an example that shows you how you could use the @JsonIgnoreType
annotation:
import com.fasterxml.jackson.annotation.JsonIgnoreType; public class PersonIgnoreType { @JsonIgnoreType public static class Address { public String streetName = null; public String houseNumber = null; public String zipCode = null; public String city = null; public String country = null; } public long personId = 0; public String name = null; public Address address = null; }
In the above example all Address
instances will be ignored.
@JsonAutoDetect
The Jackson annotation @JsonAutoDetect
is used to tell Jackson to include properties which are not
public, both when reading and writing objects. Here is an example class showing you how you can use the
@JsonAutoDetect
annotation:
import com.fasterxml.jackson.annotation.JsonAutoDetect; @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY ) public class PersonAutoDetect { private long personId = 123; public String name = null; }
The JsonAutoDetect.Visibility
class contains constants matching the visibility levels in Java,
meaning ANY
, DEFAULT
, NON_PRIVATE
, NONE
,
PROTECTED_AND_PRIVATE
and PUBLIC_ONLY
.
Read Annotations
Jackson contains a set of annotations that only affect how Jackson parses JSON into objects - meaning they affect Jackson's reading of JSON. I refer to these as "read annotations". The following sections explains Jackson's read annotations.
@JsonSetter
The Jackson annotation @JsonSetter
is used to tell Jackson that is should match the name of this setter method
to a property name in the JSON data, when reading JSON into objects. This is useful if the property names used
internally in your Java class is not the same as the property name used in the JSON file.
The following Person
class uses the name personId
for its id property:
public class Person { private long personId = 0; private String name = null; public long getPersonId() { return this.personId; } public void setPersonId(long personId) { this.personId = personId; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
But in this JSON object the name id
is used instead of personId
:
{ "id" : 1234, "name" : "John" }
Without some help Jackson cannot map the id
property from the JSON object to the personId
field of the Java class.
The @JsonSetter
annotation instructs Jackson to use a setter method for a given JSON field.
In our case we add the @JsonSetter
annotation above the setPersonId()
method. Here is
how adding the @JsonSetter
annotation looks like:
public class Person { private long personId = 0; private String name = null; public long getPersonId() { return this.personId; } @JsonSetter("id") public void setPersonId(long personId) { this.personId = personId; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
The value specified inside the @JsonSetter
annotation is the name of the JSON field to match to this
setter method. In this case the name is id
since that is the name of the field in the JSON object
we want to map to the setPersonId()
setter method.
@JsonAnySetter
The Jackson annotation @JsonAnySetter
instructs Jackson to call the same setter method for all unrecognized
fields in the JSON object. By "unrecognized" I mean all fields that are not already mapped to a property or
setter method in the Java object. Look at this Bag
class:
public class Bag { private Map<String, Object> properties = new HashMap<>(); public void set(String fieldName, Object value){ this.properties.put(fieldName, value); } public Object get(String fieldName){ return this.properties.get(fieldName); } }
And then look at this JSON object:
{ "id" : 1234, "name" : "John" }
Jackson cannot directly map the id
and name
property of this JSON object to the
Bag
class, because the Bag
class contains no public fields or setter methods.
You can tell Jackson to call the set()
method for all unrecognized fields by adding the
@JsonAnySetter
annotation, like this:
public class Bag { private Map<String, Object> properties = new HashMap<>(); @JsonAnySetter public void set(String fieldName, Object value){ this.properties.put(fieldName, value); } public Object get(String fieldName){ return this.properties.get(fieldName); } }
Now Jackson will call the set()
method with the name and value of all unrecognized fields in the JSON
object.
Keep in mind that this only has effect on unrecognized fields. If, for instance, you added a public name
property or setName(String)
method to the Bag
Java class, then the name
field in the JSON object would be mapped to that property / setter instead.
@JsonCreator
The Jackson annotation @JsonCreator
is used to tell Jackson that the Java object has a constructor
(a "creator") which can match the fields of a JSON object to the fields of the Java object.
The @JsonCreator
annotation is useful in situations where the @JsonSetter
annotation
cannot be used. For instance, immutable objects do not have any setter methods, so they need their initial values
injected into the constructor. Look at this PersonImmutable
class as example:
public class PersonImmutable { private long id = 0; private String name = null; public PersonImmutable(long id, String name) { this.id = id; this.name = name; } public long getId() { return id; } public String getName() { return name; } }
To tell Jackson that it should call the constructor of PersonImmutable
we must add the @JsonCreator
annotation to the constructor. But that alone is not enough. We must also annotate the parameters of the constructor
to tell Jackson which fields from the JSON object to pass to which constructor parameters. Here is how the
PersonImmutable
class looks with the @JsonCreator
and @JsonProperty
annotations
added:
public class PersonImmutable { private long id = 0; private String name = null; @JsonCreator public PersonImmutable( @JsonProperty("id") long id, @JsonProperty("name") String name ) { this.id = id; this.name = name; } public long getId() { return id; } public String getName() { return name; } }
Notice the annotation above the constructor and the annotations before the constructor parameters. Now Jackson
is capable of creating a PersonImmutable
from this JSON object:
{ "id" : 1234, "name" : "John" }
@JacksonInject
The Jackson annotation @JacksonInject
is used to inject values into the parsed objects, instead of
reading those values from the JSON. For instance, imagine you are downloading person JSON objects from various
different sources, and would like to know what source a given person object came from. The sources may not themselves
contain that information, but you can have Jackson inject that into the Java objects created from the JSON objects.
To mark a field in a Java class as a field that needs to have its value injected by Jackson, add the
@JacksonInject
annotation above the field. Here is an example PersonInject
class
with the @JacksonInject
annotation added above the source
field:
public class PersonInject { public long id = 0; public String name = null; @JacksonInject public String source = null; }
In order to have Jackson inject values into the source
field you need to do a little extra when
creating the Jackson ObjectMapper
. Here is what it takes to have Jackson inject values into the
Java objects:
InjectableValues inject = new InjectableValues.Std().addValue(String.class, "jenkov.com"); PersonInject personInject = new ObjectMapper().reader(inject) .forType(PersonInject.class) .readValue(new File("data/person.json"));
Notice how the value to inject into the source
attribute is set in the InjectableValues
addValue()
method. Notice also that the value is only tied to the type String
- not to
any specific field name. It is the @JacksonInject
annotation that specifies what field the value is
to be injected into.
If you were to download person JSON objects from multiple sources and have a different source value injected for each source, you would have to repeat the above code for each source.
@JsonDeserialize
The Jackson annotation @JsonDeserialize
is used to specify a custom de-serializer class for a given
field in a Java object. For instance, imagine you wanted to optimize the on-the-wire formatting of the boolean
values false
and true
to 0
and 1
.
First you would need to add the @JsonDeserialize
annotation to the field you want to use the custom
deserializer for. Here is how adding the @JsonDeserialize
annotation to a field looks like:
public class PersonDeserialize { public long id = 0; public String name = null; @JsonDeserialize(using = OptimizedBooleanDeserializer.class) public boolean enabled = false; }
Second, here is what the OptimizedBooleanDeserializer
class referenced from the @JsonDeserialize
annotation looks like:
public class OptimizedBooleanDeserializer extends JsonDeserializer<Boolean> { @Override public Boolean deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { String text = jsonParser.getText(); if("0".equals(text)) return false; return true; } }
Notice that the OptimizedBooleanDeserializer
class extends JsonDeserializer
with the
generic type Boolean
. Doing so makes the deserialize()
method return a Boolean
object. If you were to deserialize another type (e.g a java.util.Date
) you would have to specify
that type inside the generics brackets (if you are new to Java generics, I have a
Java Generics Tutorial too).
You obtain the value of the field to deserialize by calling the getText()
method of the
jsonParser
parameter. You can then deserialize that text into whatever value and type your
deserializer is targeted at (a Boolean
in this example).
Finally you need to see what it looks like to deserialize an object with a custom deserializer and the
@JsonDeserializer
annotation:
PersonDeserialize person = objectMapper .reader(PersonDeserialize.class) .readValue(new File("data/person-optimized-boolean.json"));
Notice how we first need to create a reader for the PersonDeserialize
class using the
ObjectMapper
's reader()
method, and then we call readValue()
on the object returned by that method.
Write Annotations
Jackson also contains a set of annotations that can influence how Jackson serializes (writes) Java objects to JSON. Each of these write (serialization) annotations will be covered in the following sections.
@JsonInclude
The Jackson annotation @JsonInclude
tells Jackson only to include properties under certain
circumstances. For instance, that properties should only be included if they are non-null, non-empty, or have
non-default values. Here is an example that shows how you can use the @JsonInclude
annotation:
import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_EMPTY) public class PersonInclude { public long personId = 0; public String name = null; }
This example will only include the name
property if the value set for it is non-empty, meaning
is not null and is not an empty string.
A more saying name for the @JsonInclude
annotation could have been @JsonIncludeOnlyWhen
,
but that would have been longer to write.
@JsonGetter
The @JsonGetter
Jackson annotation is used to tell Jackson that a certain field value should be
obtained from calling a getter
method instead of via direct field access. The @JsonGetter
annotation is useful if your Java class uses jQuery style for getter and setter names. For instance, instead of
getPersonId()
and setPersonId()
you might have the methods personId()
and
personId(long id)
.
Here is an example class named PersonGetter
which shows the use of the
@JsonGetter
annotation:
public class PersonGetter { private long personId = 0; @JsonGetter("id") public long personId() { return this.personId; } @JsonSetter("id") public void personId(long personId) { this.personId = personId; } }
As you can see, the personId()
method is annotated with the @JsonGetter
annotation.
The value set on the @JsonGetter
annotation is the name that should be used in the JSON object. Thus,
the name used for the personId
in the JSON object is id
. The resulting JSON object would
look like this:
{"id":0}
Notice also that the personId(long personId)
method is annotated with the @JsonSetter
annotation to make Jackson recognized that as the setter matching the id
attribute in the JSON object.
The @JsonSetter
annotation is used when reading from JSON into Java objects - not when writing
Java objects to JSON. The @JsonSetter
annotation is just included for the sake of completeness.
@JsonAnyGetter
The @JsonAnyGetter
Jackson annotation enables you to use a Map
as container for
properties that you want to serialize to JSON. Here is an example of using the @JsonAnyGetter
annotation in a Java class:
public class PersonAnyGetter { private Map<String, Object> properties = new HashMap<>(); @JsonAnyGetter public Map<String, Object> properties() { return properties; } }
When seeing the @JsonAnyGetter
annotation Jackson will obtain the Map
returned
from the method which @JsonAnyGetter
annotates, and will treat each key-value pair in that
Map
as a property. In other words, all key-value pairs in the Map
will be
serialized to JSON as part of the PersonAnyGetter
object.
@JsonPropertyOrder
The @JsonPropertyOrder
Jackson annotation can be used to specify in what order the fields of your
Java object should be serialized into JSON. Here is an example showing how to use the @JsonPropertyOrder
annotation:
@JsonPropertyOrder({"name", "personId"}) public class PersonPropertyOrder { public long personId = 0; public String name = null; }
Normally Jackson would have serialized the properties in PersonPropertyOrder
in the sequence they
are found in the class. However, the @JsonPropertyOrder
annotation specifies a different order
where the name
property will be appear first and the personId
property second in the
serialized JSON output.
@JsonRawValue
The @JsonRawValue
Jackson annotation tells Jackson that this property value should written directly as
it is to the JSON output. If the property is a String
Jackson would normally have enclosed the value
in quotation marks, but if annotated with the @JsonRawValue
property Jackson won't do that.
To make it more clear what @JsonRawValue
does, look at this class without the @JsonRawValue
in use:
public class PersonRawValue { public long personId = 0; public String address = "$#"; }
Jackson would serialize this into this JSON string:
{"personId":0,"address":"$#"}
Now we add the @JsonRawValue
to the address
property, like this:
public class PersonRawValue { public long personId = 0; @JsonRawValue public String address = "$#"; }
Jackson will now omit the quotation marks when serializing the address
property. The serialized JSON
will thus look like this:
{"personId":0,"address":$#}
This is of course invalid JSON, so why would you want that?
Well, if the address
property contained a JSON string then that JSON string would be serialized into
the final JSON object as part of the JSON object structure, and not just into a string in the
address
field in the JSON object. To see how that would work, let us change the value of the
address
property like this:
public class PersonRawValue { public long personId = 0; @JsonRawValue public String address = "{ \"street\" : \"Wall Street\", \"no\":1}"; }
Jackson would serialize this into this JSON:
{"personId":0,"address":{ "street" : "Wall Street", "no":1}}
Notice how the JSON string is now part of the serialized JSON structure.
Without the @JsonRawValue
annotation Jackson would have serialized the object to this JSON:
{"personId":0,"address":"{ \"street\" : \"Wall Street\", \"no\":1}"}
Notice how the value of the address
property is now enclosed in quotation marks, and all the quotation
marks inside the value are escaped.
@JsonValue
The Jackson annotation @JsonValue
tells Jackson that Jackson should not attempt to serialize the
object itself, but rather call a method on the object which serializes the object to a JSON string. Note that
Jackson will escape any quotation marks inside the String returned by the custom serialization, so you cannot
return e.g. a full JSON object. For that you should use @JsonRawValue
instead (see previous section).
The @JsonValue
annotation is added to the method that Jackson is to call to serialize the object
into a JSON string. Here is an example showing how to use the @JsonValue
annotation:
public class PersonValue { public long personId = 0; public String name = null; @JsonValue public String toJson(){ return this.personId + "," + this.name; } }
The output you would get from asking Jackson to serialize a PersonValue
object is this:
"0,null"
The quotation marks are added by Jackson. Remember, any quotation marks in the value string returned by the object are escaped.
@JsonSerialize
The @JsonSerialize
Jackson annotation is used to specify a custom serializer for a field in a
Java object. Here is an example Java class that uses the @JsonSerialize
annotation:
public class PersonSerializer { public long personId = 0; public String name = "John"; @JsonSerialize(using = OptimizedBooleanSerializer.class) public boolean enabled = false; }
Notice the @JsonSerialize
annotation above the enabled
field.
The OptimizedBooleanSerializer
will serialize a true
value to 1
and a
false
value 0
. Here is the code:
public class OptimizedBooleanSerializer extends JsonSerializer<Boolean> { @Override public void serialize(Boolean aBoolean, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException { if(aBoolean){ jsonGenerator.writeNumber(1); } else { jsonGenerator.writeNumber(0); } } }
Tweet | |
Jakob Jenkov |