Перейти к основному содержимому

Примеры аннотаций Джексона

· 18 мин. чтения

Задача: Медиана двух отсортированных массивов

Даны два отсортированных массива размерами n и m. Найдите медиану слияния этих двух массивов.
Временная сложность решения должна быть O(log(m + n)) ...

ANDROMEDA

1. Обзор

В этом уроке мы углубимся в аннотации Джексона .

Мы увидим, как использовать существующие аннотации, как создавать собственные и, наконец, как их отключить.

2. Аннотации сериализации Джексона

Во-первых, мы рассмотрим аннотации сериализации.

2.1. @JsonAnyGetter

Аннотация @JsonAnyGetter обеспечивает гибкость использования поля карты в качестве стандартных свойств.

Например, сущность ExtendableBean имеет свойство name и набор расширяемых атрибутов в виде пар ключ/значение:

public class ExtendableBean {
public String name;
private Map<String, String> properties;

@JsonAnyGetter
public Map<String, String> getProperties() {
return properties;
}
}

Когда мы сериализуем экземпляр этой сущности, мы получаем все ключи-значения в карте как стандартные простые свойства:

{
"name":"My bean",
"attr2":"val2",
"attr1":"val1"
}

Вот как сериализация этой сущности выглядит на практике:

@Test
public void whenSerializingUsingJsonAnyGetter_thenCorrect()
throws JsonProcessingException {

ExtendableBean bean = new ExtendableBean("My bean");
bean.add("attr1", "val1");
bean.add("attr2", "val2");

String result = new ObjectMapper().writeValueAsString(bean);

assertThat(result, containsString("attr1"));
assertThat(result, containsString("val1"));
}

Мы также можем использовать необязательный аргумент , включенный как false , чтобы отключить @JsonAnyGetter(). В этом случае карта будет преобразована в формат JSON и появится в переменной свойств после сериализации.

2.2. @JsonGetter

Аннотация @JsonGetter является альтернативой аннотации @JsonProperty , которая помечает метод как метод получения.

В следующем примере мы указываем метод getTheName() в качестве метода получения свойства имени объекта MyBean :

public class MyBean {
public int id;
private String name;

@JsonGetter("name")
public String getTheName() {
return name;
}
}

Вот как это работает на практике:

@Test
public void whenSerializingUsingJsonGetter_thenCorrect()
throws JsonProcessingException {

MyBean bean = new MyBean(1, "My bean");

String result = new ObjectMapper().writeValueAsString(bean);

assertThat(result, containsString("My bean"));
assertThat(result, containsString("1"));
}

2.3. @JsonPropertyOrder

Мы можем использовать аннотацию @JsonPropertyOrder , чтобы указать порядок свойств при сериализации .

Давайте установим собственный порядок свойств сущности MyBean :

@JsonPropertyOrder({ "name", "id" })
public class MyBean {
public int id;
public String name;
}

Вот результат сериализации:

{
"name":"My bean",
"id":1
}

Затем мы можем провести простой тест:

@Test
public void whenSerializingUsingJsonPropertyOrder_thenCorrect()
throws JsonProcessingException {

MyBean bean = new MyBean(1, "My bean");

String result = new ObjectMapper().writeValueAsString(bean);
assertThat(result, containsString("My bean"));
assertThat(result, containsString("1"));
}

Мы также можем использовать @JsonPropertyOrder(alphabetic=true) для упорядочения свойств в алфавитном порядке. В этом случае вывод сериализации будет:

{
"id":1,
"name":"My bean"
}

2.4. @JsonRawValue

Аннотация @JsonRawValue может указать Джексону сериализовать свойство точно так, как оно есть .

В следующем примере мы используем @JsonRawValue для встраивания некоторого пользовательского JSON в качестве значения сущности:

public class RawBean {
public String name;

@JsonRawValue
public String json;
}

Результат сериализации объекта:

{
"name":"My bean",
"json":{
"attr":false
}
}

Далее простой тест:

@Test
public void whenSerializingUsingJsonRawValue_thenCorrect()
throws JsonProcessingException {

RawBean bean = new RawBean("My bean", "{\"attr\":false}");

String result = new ObjectMapper().writeValueAsString(bean);
assertThat(result, containsString("My bean"));
assertThat(result, containsString("{\"attr\":false}"));
}

Мы также можем использовать необязательное логическое значение аргумента , которое определяет, активна эта аннотация или нет.

2.5. @JsonValue

@JsonValue указывает единственный метод, который библиотека будет использовать для сериализации всего экземпляра.

Например, в перечислении мы аннотируем getName с помощью @JsonValue , чтобы любой такой объект был сериализован через его имя:

public enum TypeEnumWithValue {
TYPE1(1, "Type A"), TYPE2(2, "Type 2");

private Integer id;
private String name;

// standard constructors

@JsonValue
public String getName() {
return name;
}
}

А теперь наш тест:

@Test
public void whenSerializingUsingJsonValue_thenCorrect()
throws JsonParseException, IOException {

String enumAsString = new ObjectMapper()
.writeValueAsString(TypeEnumWithValue.TYPE1);

assertThat(enumAsString, is(""Type A""));
}

2.6. @JsonRootName

Аннотация @JsonRootName используется , если обертка включена, чтобы указать имя используемой корневой оболочки.

Обертывание означает, что вместо сериализации пользователя что-то вроде:

{
"id": 1,
"name": "John"
}

Это будет завернуто так:

{
"User": {
"id": 1,
"name": "John"
}
}

Итак, давайте посмотрим на пример. Мы собираемся использовать аннотацию @JsonRootName , чтобы указать имя этой потенциальной сущности-оболочки : ``

@JsonRootName(value = "user")
public class UserWithRoot {
public int id;
public String name;
}

По умолчанию именем обёртки будет имя класса — UserWithRoot . Используя аннотацию, мы получаем более аккуратного пользователя:

@Test
public void whenSerializingUsingJsonRootName_thenCorrect()
throws JsonProcessingException {

UserWithRoot user = new User(1, "John");

ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
String result = mapper.writeValueAsString(user);

assertThat(result, containsString("John"));
assertThat(result, containsString("user"));
}

Вот результат сериализации:

{
"user":{
"id":1,
"name":"John"
}
}

Начиная с версии Jackson 2.4, доступно новое дополнительное пространство имен аргументов для использования с такими форматами данных, как XML. Если мы добавим его, оно станет частью полного имени:

@JsonRootName(value = "user", namespace="users")
public class UserWithRootNamespace {
public int id;
public String name;

// ...
}

Если мы сериализуем его с помощью XmlMapper, результат будет таким:

<user xmlns="users">
<id xmlns="">1</id>
<name xmlns="">John</name>
<items xmlns=""/>
</user>

2.7. @JsonSerialize

@JsonSerialize указывает пользовательский сериализатор, который следует использовать при маршаллинге объекта.

Давайте посмотрим на быстрый пример. Мы собираемся использовать @JsonSerialize для сериализации свойства eventDate с помощью CustomDateSerializer :

public class EventWithSerializer {
public String name;

@JsonSerialize(using = CustomDateSerializer.class)
public Date eventDate;
}

Вот простой пользовательский сериализатор Джексона:

public class CustomDateSerializer extends StdSerializer<Date> {

private static SimpleDateFormat formatter
= new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

public CustomDateSerializer() {
this(null);
}

public CustomDateSerializer(Class<Date> t) {
super(t);
}

@Override
public void serialize(
Date value, JsonGenerator gen, SerializerProvider arg2)
throws IOException, JsonProcessingException {
gen.writeString(formatter.format(value));
}
}

Теперь давайте используем их в тесте:

@Test
public void whenSerializingUsingJsonSerialize_thenCorrect()
throws JsonProcessingException, ParseException {

SimpleDateFormat df
= new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

String toParse = "20-12-2014 02:30:00";
Date date = df.parse(toParse);
EventWithSerializer event = new EventWithSerializer("party", date);

String result = new ObjectMapper().writeValueAsString(event);
assertThat(result, containsString(toParse));
}

3. Аннотации десериализации Джексона

Далее давайте рассмотрим аннотации десериализации Джексона.

3.1. @JsonCreator

Мы можем использовать аннотацию @JsonCreator для настройки конструктора/фабрики, используемой при десериализации.

Это очень полезно, когда нам нужно десериализовать некоторый JSON, который не совсем соответствует целевому объекту, который нам нужно получить.

Давайте посмотрим на пример. Скажем, нам нужно десериализовать следующий JSON:

{
"id":1,
"theName":"My bean"
}

Однако в нашей целевой сущности нет поля theName , есть только поле name . Теперь мы не хотим изменять сам объект, нам просто нужно немного больше контролировать процесс десортировки, аннотируя конструктор с помощью @JsonCreator, а также используя аннотацию @JsonProperty :

public class BeanWithCreator {
public int id;
public String name;

@JsonCreator
public BeanWithCreator(
@JsonProperty("id") int id,
@JsonProperty("theName") String name) {
this.id = id;
this.name = name;
}
}

Давайте посмотрим на это в действии:

@Test
public void whenDeserializingUsingJsonCreator_thenCorrect()
throws IOException {

String json = "{\"id\":1,\"theName\":\"My bean\"}";

BeanWithCreator bean = new ObjectMapper()
.readerFor(BeanWithCreator.class)
.readValue(json);
assertEquals("My bean", bean.name);
}

3.2. @JacksonInject

@JacksonInject указывает, что свойство получит свое значение из инъекции, а не из данных JSON.

В следующем примере мы используем @JacksonInject для внедрения идентификатора свойства :

public class BeanWithInject {
@JacksonInject
public int id;

public String name;
}

Вот как это работает:

@Test
public void whenDeserializingUsingJsonInject_thenCorrect()
throws IOException {

String json = "{\"name\":\"My bean\"}";

InjectableValues inject = new InjectableValues.Std()
.addValue(int.class, 1);
BeanWithInject bean = new ObjectMapper().reader(inject)
.forType(BeanWithInject.class)
.readValue(json);

assertEquals("My bean", bean.name);
assertEquals(1, bean.id);
}

3.3. @JsonAnySetter

@JsonAnySetter позволяет нам гибко использовать карту в качестве стандартных свойств. При десериализации свойства из JSON будут просто добавлены на карту.

Во- первых, мы будем использовать @JsonAnySetter для десериализации сущности ExtendableBean :

public class ExtendableBean {
public String name;
private Map<String, String> properties;

@JsonAnySetter
public void add(String key, String value) {
properties.put(key, value);
}
}

Это JSON, который нам нужно десериализовать:

{
"name":"My bean",
"attr2":"val2",
"attr1":"val1"
}

Тогда вот как все это связано вместе:

@Test
public void whenDeserializingUsingJsonAnySetter_thenCorrect()
throws IOException {
String json
= "{\"name\":\"My bean\",\"attr2\":\"val2\",\"attr1\":\"val1\"}";

ExtendableBean bean = new ObjectMapper()
.readerFor(ExtendableBean.class)
.readValue(json);

assertEquals("My bean", bean.name);
assertEquals("val2", bean.getProperties().get("attr2"));
}

3.4. @JsonSetter

@JsonSetter — это альтернатива @JsonProperty , которая помечает метод как метод установки.

Это невероятно полезно, когда нам нужно прочитать некоторые данные JSON, но целевой класс сущности не совсем соответствует этим данным , и поэтому нам нужно настроить процесс, чтобы он соответствовал.

В следующем примере мы укажем метод s etTheName() в качестве установщика свойства name в нашей сущности MyBean :

public class MyBean {
public int id;
private String name;

@JsonSetter("name")
public void setTheName(String name) {
this.name = name;
}
}

Теперь, когда нам нужно распаковать некоторые данные JSON, это работает отлично:

@Test
public void whenDeserializingUsingJsonSetter_thenCorrect()
throws IOException {

String json = "{\"id\":1,\"name\":\"My bean\"}";

MyBean bean = new ObjectMapper()
.readerFor(MyBean.class)
.readValue(json);
assertEquals("My bean", bean.getTheName());
}

3.5. @JsonDeserialize

@JsonDeserialize указывает на использование пользовательского десериализатора.

Во- первых, мы будем использовать @JsonDeserialize для десериализации свойства eventDate с помощью CustomDateDeserializer :

public class EventWithSerializer {
public String name;

@JsonDeserialize(using = CustomDateDeserializer.class)
public Date eventDate;
}

Вот пользовательский десериализатор:

public class CustomDateDeserializer
extends StdDeserializer<Date> {

private static SimpleDateFormat formatter
= new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

public CustomDateDeserializer() {
this(null);
}

public CustomDateDeserializer(Class<?> vc) {
super(vc);
}

@Override
public Date deserialize(
JsonParser jsonparser, DeserializationContext context)
throws IOException {

String date = jsonparser.getText();
try {
return formatter.parse(date);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}

Далее идет встречный тест:

@Test
public void whenDeserializingUsingJsonDeserialize_thenCorrect()
throws IOException {

String json
= "{"name":"party","eventDate":"20-12-2014 02:30:00"}";

SimpleDateFormat df
= new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
EventWithSerializer event = new ObjectMapper()
.readerFor(EventWithSerializer.class)
.readValue(json);

assertEquals(
"20-12-2014 02:30:00", df.format(event.eventDate));
}

3.6. @JsonAlias

@JsonAlias определяет одно или несколько альтернативных имен свойства во время десериализации .

Давайте посмотрим, как эта аннотация работает на быстром примере:

public class AliasBean {
@JsonAlias({ "fName", "f_name" })
private String firstName;
private String lastName;
}

Здесь у нас есть POJO, и мы хотим десериализовать JSON со значениями, такими как fName , f_name и firstName , в переменную firstName POJO.

Ниже приведен тест, подтверждающий, что эта аннотация работает должным образом:

@Test
public void whenDeserializingUsingJsonAlias_thenCorrect() throws IOException {
String json = "{\"fName\": \"John\", \"lastName\": \"Green\"}";
AliasBean aliasBean = new ObjectMapper().readerFor(AliasBean.class).readValue(json);
assertEquals("John", aliasBean.getFirstName());
}

4. Аннотации включения собственности Джексона

4.1. @JsonIgnoreProperties

@JsonIgnoreProperties — это аннотация на уровне класса, которая помечает свойство или список свойств, которые Джексон будет игнорировать.

Давайте рассмотрим быстрый пример игнорирования идентификатора свойства при сериализации:

@JsonIgnoreProperties({ "id" })
public class BeanWithIgnore {
public int id;
public String name;
}

Теперь вот тест, чтобы убедиться, что игнор происходит:

@Test
public void whenSerializingUsingJsonIgnoreProperties_thenCorrect()
throws JsonProcessingException {

BeanWithIgnore bean = new BeanWithIgnore(1, "My bean");

String result = new ObjectMapper()
.writeValueAsString(bean);

assertThat(result, containsString("My bean"));
assertThat(result, not(containsString("id")));
}

Чтобы игнорировать любые неизвестные свойства во входных данных JSON без исключения, мы можем установить ignoreUnknown=true в аннотации @JsonIgnoreProperties .

4.2. @JsonIgnore

Напротив, аннотация @JsonIgnore используется для пометки игнорируемого свойства на уровне поля.

Давайте используем @JsonIgnore , чтобы игнорировать идентификатор свойства из сериализации:

public class BeanWithIgnore {
@JsonIgnore
public int id;

public String name;
}

Затем мы проверим, чтобы убедиться, что id был успешно проигнорирован:

@Test
public void whenSerializingUsingJsonIgnore_thenCorrect()
throws JsonProcessingException {

BeanWithIgnore bean = new BeanWithIgnore(1, "My bean");

String result = new ObjectMapper()
.writeValueAsString(bean);

assertThat(result, containsString("My bean"));
assertThat(result, not(containsString("id")));
}

4.3. @JsonIgnoreType

@JsonIgnoreType помечает все свойства аннотированного типа как игнорируемые.

Мы можем использовать аннотацию, чтобы пометить все свойства типа Name как игнорируемые:

public class User {
public int id;
public Name name;

@JsonIgnoreType
public static class Name {
public String firstName;
public String lastName;
}
}

Мы также можем проверить правильность работы игнорирования:

@Test
public void whenSerializingUsingJsonIgnoreType_thenCorrect()
throws JsonProcessingException, ParseException {

User.Name name = new User.Name("John", "Doe");
User user = new User(1, name);

String result = new ObjectMapper()
.writeValueAsString(user);

assertThat(result, containsString("1"));
assertThat(result, not(containsString("name")));
assertThat(result, not(containsString("John")));
}

4.4. @JsonInclude

Мы можем использовать @JsonInclude для исключения свойств с пустыми/нулевыми значениями/значениями по умолчанию.

Давайте рассмотрим пример исключения нулей из сериализации:

@JsonInclude(Include.NON_NULL)
public class MyBean {
public int id;
public String name;
}

Вот полный тест:

public void whenSerializingUsingJsonInclude_thenCorrect()
throws JsonProcessingException {

MyBean bean = new MyBean(1, null);

String result = new ObjectMapper()
.writeValueAsString(bean);

assertThat(result, containsString("1"));
assertThat(result, not(containsString("name")));
}

4.5. @JsonAutoDetect

@JsonAutoDetect может переопределить семантику по умолчанию , определяющую, какие свойства видны, а какие нет .

Во-первых, давайте посмотрим, как аннотация может быть очень полезной, на простом примере; давайте включим сериализацию частных свойств:

@JsonAutoDetect(fieldVisibility = Visibility.ANY)
public class PrivateBean {
private int id;
private String name;
}

Затем тест:

@Test
public void whenSerializingUsingJsonAutoDetect_thenCorrect()
throws JsonProcessingException {

PrivateBean bean = new PrivateBean(1, "My bean");

String result = new ObjectMapper()
.writeValueAsString(bean);

assertThat(result, containsString("1"));
assertThat(result, containsString("My bean"));
}

5. Аннотации обработки полиморфных типов Джексона

Далее давайте взглянем на аннотации обработки полиморфного типа Джексона:

  • @JsonTypeInfo — указывает сведения о том, какую информацию о типе следует включать в сериализацию.
  • @JsonSubTypes — указывает подтипы аннотированного типа.
  • @JsonTypeName — определяет имя логического типа для использования в аннотированном классе .

Давайте рассмотрим более сложный пример и используем все три — @JsonTypeInfo , @JsonSubTypes и @JsonTypeName — для сериализации/десериализации сущности Zoo :

public class Zoo {
public Animal animal;

@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = As.PROPERTY,
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "dog"),
@JsonSubTypes.Type(value = Cat.class, name = "cat")
})
public static class Animal {
public String name;
}

@JsonTypeName("dog")
public static class Dog extends Animal {
public double barkVolume;
}

@JsonTypeName("cat")
public static class Cat extends Animal {
boolean likesCream;
public int lives;
}
}

Когда мы делаем сериализацию:

@Test
public void whenSerializingPolymorphic_thenCorrect()
throws JsonProcessingException {
Zoo.Dog dog = new Zoo.Dog("lacy");
Zoo zoo = new Zoo(dog);

String result = new ObjectMapper()
.writeValueAsString(zoo);

assertThat(result, containsString("type"));
assertThat(result, containsString("dog"));
}

Here's what serializing the Zoo instance with the Dog will result in:

{
"animal": {
"type": "dog",
"name": "lacy",
"barkVolume": 0
}
}

Now for de-serialization. Let's start with the following JSON input:

{
"animal":{
"name":"lacy",
"type":"cat"
}
}

Then let's see how that gets unmarshalled to a Zoo instance:

@Test
public void whenDeserializingPolymorphic_thenCorrect()
throws IOException {
String json = "{\"animal\":{\"name\":\"lacy\",\"type\":\"cat\"}}";

Zoo zoo = new ObjectMapper()
.readerFor(Zoo.class)
.readValue(json);

assertEquals("lacy", zoo.animal.name);
assertEquals(Zoo.Cat.class, zoo.animal.getClass());
}

6. Jackson General Annotations

Next let's discuss some of Jackson's more general annotations.

6.1. @JsonProperty

We can add the @JsonProperty annotation to indicate the property name in JSON .

Let's use @JsonProperty to serialize/deserialize the property name when we're dealing with non-standard getters and setters:

public class MyBean {
public int id;
private String name;

@JsonProperty("name")
public void setTheName(String name) {
this.name = name;
}

@JsonProperty("name")
public String getTheName() {
return name;
}
}

Next is our test:

@Test
public void whenUsingJsonProperty_thenCorrect()
throws IOException {
MyBean bean = new MyBean(1, "My bean");

String result = new ObjectMapper().writeValueAsString(bean);

assertThat(result, containsString("My bean"));
assertThat(result, containsString("1"));

MyBean resultBean = new ObjectMapper()
.readerFor(MyBean.class)
.readValue(result);
assertEquals("My bean", resultBean.getTheName());
}

6.2. @JsonFormat

The @JsonFormat annotation specifies a format when serializing Date/Time values.

In the following example, we use @JsonFormat to control the format of the property eventDate :

public class EventWithFormat {
public String name;

@JsonFormat(
shape = JsonFormat.Shape.STRING,
pattern = "dd-MM-yyyy hh:mm:ss")
public Date eventDate;
}

Then here's the test:

@Test
public void whenSerializingUsingJsonFormat_thenCorrect()
throws JsonProcessingException, ParseException {
SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
df.setTimeZone(TimeZone.getTimeZone("UTC"));

String toParse = "20-12-2014 02:30:00";
Date date = df.parse(toParse);
EventWithFormat event = new EventWithFormat("party", date);

String result = new ObjectMapper().writeValueAsString(event);

assertThat(result, containsString(toParse));
}

6.3. @JsonUnwrapped

@JsonUnwrapped defines values that should be unwrapped/flattened when serialized/deserialized.

Let's see exactly how this works; we'll use the annotation to unwrap the property name :

public class UnwrappedUser {
public int id;

@JsonUnwrapped
public Name name;

public static class Name {
public String firstName;
public String lastName;
}
}

Now let's serialize an instance of this class:

@Test
public void whenSerializingUsingJsonUnwrapped_thenCorrect()
throws JsonProcessingException, ParseException {
UnwrappedUser.Name name = new UnwrappedUser.Name("John", "Doe");
UnwrappedUser user = new UnwrappedUser(1, name);

String result = new ObjectMapper().writeValueAsString(user);

assertThat(result, containsString("John"));
assertThat(result, not(containsString("name")));
}

Finally, here's what the output looks like – the fields of the static nested class unwrapped along with the other field:

{
"id":1,
"firstName":"John",
"lastName":"Doe"
}

6.4. @JsonView

@JsonView indicates the View in which the property will be included for serialization/deserialization.

For example, we'll use @JsonView to serialize an instance of Item entity.

First, let's start with the views:

public class Views {
public static class Public {}
public static class Internal extends Public {}
}

Next here's the Item entity using the views:

public class Item {
@JsonView(Views.Public.class)
public int id;

@JsonView(Views.Public.class)
public String itemName;

@JsonView(Views.Internal.class)
public String ownerName;
}

Finally, the full test:

@Test
public void whenSerializingUsingJsonView_thenCorrect()
throws JsonProcessingException {
Item item = new Item(2, "book", "John");

String result = new ObjectMapper()
.writerWithView(Views.Public.class)
.writeValueAsString(item);

assertThat(result, containsString("book"));
assertThat(result, containsString("2"));
assertThat(result, not(containsString("John")));
}

6.5. @JsonManagedReference, @JsonBackReference

The @JsonManagedReference and @JsonBackReference annotations can handle parent/child relationships and work around loops.

In the following example, we use @JsonManagedReference and @JsonBackReference to serialize our ItemWithRef entity:

public class ItemWithRef {
public int id;
public String itemName;

@JsonManagedReference
public UserWithRef owner;
}

Our UserWithRef entity:

public class UserWithRef {
public int id;
public String name;

@JsonBackReference
public List<ItemWithRef> userItems;
}

Then the test:

@Test
public void whenSerializingUsingJacksonReferenceAnnotation_thenCorrect()
throws JsonProcessingException {
UserWithRef user = new UserWithRef(1, "John");
ItemWithRef item = new ItemWithRef(2, "book", user);
user.addItem(item);

String result = new ObjectMapper().writeValueAsString(item);

assertThat(result, containsString("book"));
assertThat(result, containsString("John"));
assertThat(result, not(containsString("userItems")));
}

6.6. @JsonIdentityInfo

@JsonIdentityInfo indicates that Object Identity should be used when serializing/deserializing values, like when dealing with infinite recursion types of problems, for instance.

In the following example, we have an ItemWithIdentity entity with a bidirectional relationship with the UserWithIdentity entity:

@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class ItemWithIdentity {
public int id;
public String itemName;
public UserWithIdentity owner;
}

The UserWithIdentity entity:

@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class UserWithIdentity {
public int id;
public String name;
public List<ItemWithIdentity> userItems;
}

Now let's see how the infinite recursion problem is handled :

@Test
public void whenSerializingUsingJsonIdentityInfo_thenCorrect()
throws JsonProcessingException {
UserWithIdentity user = new UserWithIdentity(1, "John");
ItemWithIdentity item = new ItemWithIdentity(2, "book", user);
user.addItem(item);

String result = new ObjectMapper().writeValueAsString(item);

assertThat(result, containsString("book"));
assertThat(result, containsString("John"));
assertThat(result, containsString("userItems"));
}

Here's the full output of the serialized item and user:

{
"id": 2,
"itemName": "book",
"owner": {
"id": 1,
"name": "John",
"userItems": [
2
]
}
}

6.7. @JsonFilter

The @JsonFilter annotation specifies a filter to use during serialization.

First, we define the entity and we point to the filter:

@JsonFilter("myFilter")
public class BeanWithFilter {
public int id;
public String name;
}

Now in the full test, we define the filter, which excludes all other properties except name from serialization:

@Test
public void whenSerializingUsingJsonFilter_thenCorrect()
throws JsonProcessingException {
BeanWithFilter bean = new BeanWithFilter(1, "My bean");

FilterProvider filters
= new SimpleFilterProvider().addFilter(
"myFilter",
SimpleBeanPropertyFilter.filterOutAllExcept("name"));

String result = new ObjectMapper()
.writer(filters)
.writeValueAsString(bean);

assertThat(result, containsString("My bean"));
assertThat(result, not(containsString("id")));
}

7. Custom Jackson Annotation

Next let's see how to create a custom Jackson annotation. We can make use of the @JacksonAnnotationsInside annotation:

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonInclude(Include.NON_NULL)
@JsonPropertyOrder({ "name", "id", "dateCreated" })
public @interface CustomAnnotation {}

Now if we use the new annotation on an entity:

@CustomAnnotation
public class BeanWithCustomAnnotation {
public int id;
public String name;
public Date dateCreated;
}

We can see how it combines the existing annotations into a simple custom one that we can use as a shorthand:

@Test
public void whenSerializingUsingCustomAnnotation_thenCorrect()
throws JsonProcessingException {
BeanWithCustomAnnotation bean
= new BeanWithCustomAnnotation(1, "My bean", null);

String result = new ObjectMapper().writeValueAsString(bean);

assertThat(result, containsString("My bean"));
assertThat(result, containsString("1"));
assertThat(result, not(containsString("dateCreated")));
}

The output of the serialization process:

{
"name":"My bean",
"id":1
}

8. Jackson MixIn Annotations

Next let's see how to use Jackson MixIn annotations.

For example, let's use the MixIn annotations to ignore properties of type User :

public class Item {
public int id;
public String itemName;
public User owner;
}
@JsonIgnoreType
public class MyMixInForIgnoreType {}

Then let's see this in action:

@Test
public void whenSerializingUsingMixInAnnotation_thenCorrect()
throws JsonProcessingException {
Item item = new Item(1, "book", null);

String result = new ObjectMapper().writeValueAsString(item);
assertThat(result, containsString("owner"));

ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(User.class, MyMixInForIgnoreType.class);

result = mapper.writeValueAsString(item);
assertThat(result, not(containsString("owner")));
}

9. Disable Jackson Annotation

Finally, let's see how we can disable all Jackson annotations . We can do this by disabling the MapperFeature. USE_ANNOTATIONS as in the following example:

@JsonInclude(Include.NON_NULL)
@JsonPropertyOrder({ "name", "id" })
public class MyBean {
public int id;
public String name;
}

Now, after disabling annotations, these should have no effect and the defaults of the library should apply:

@Test
public void whenDisablingAllAnnotations_thenAllDisabled()
throws IOException {
MyBean bean = new MyBean(1, null);

ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.USE_ANNOTATIONS);
String result = mapper.writeValueAsString(bean);

assertThat(result, containsString("1"));
assertThat(result, containsString("name"));
}

The result of serialization before disabling annotations:

{"id":1}

The result of serialization after disabling annotations:

{
"id":1,
"name":null
}

10. Conclusion

In this article, we examined Jackson annotations, just scratching the surface of the kind of flexibility we can get by using them correctly.

The implementation of all of these examples and code snippets can be found over on GitHub .