Аннотации Jackson

К оглавлению Jackson.

1. Обзор

В этой статье мы рассмотрим аннотации Jackson.
Мы изучим как использовать существующие аннотации Jackson и как создать свои.
А также как их отключить.

2. Аннотации Jackson для сериализации

Для начала рассмотрим аннотации применяемые для сериализации объектов.

2.1 @JsonAnyGetter

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

Пример — класс ExtendableBean имеет фиксированное свойство name и поле с типом Map для задания произвольного количества атрибутов в виде пар key/value:

https://www.high-endrolex.com/28
public class ExtendableBean {
    public String name;
    private Map<String, String> properties;
 
    @JsonAnyGetter
    public Map<String, String> getProperties() {
        return properties;
    }
}

Сериализация без аннотации @JsonAnyGetter:

{
    "name": "BeanName",
    "properties": {
        "attr2": "value2",
        "attr1": "value1"
    }
}

С аннотацией @JsonAnyGetter Jackson сериализует атрибуты key-value как обычные свойствами объекта, без вложенности:

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

2.2. @JsonGetter

Аннотация @JsonGetter (альтернатива аннотации @JsonProperty), которой можно пометить определённый метод как геттер.

В следующем примере помечаем разные методы класса original siteelf bar 600 vapetelefoonhoesjes se 2020www.adiuc.org/MyBean аннотациями @JsonGetter :

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

    // свойство name существует
    @JsonGetter("name")
    public String getTheName() {
        return name;
    }

    // название метода без get...
    @JsonGetter
    public String testMethod1() {
        return "testMethod1";
    }

    // название метода с get...
    @JsonGetter
    public String getTestMethod2() {
        return "getTestMethod2";
    }

    // // свойство myProperty не существует
    @JsonGetter("myProperty")
    public String testMethod3() {
        return "testMethod3";
    }
}

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

{
    "name": "BeanName",              // getTheName --> name
    "testMethod1": "testMethod1",
    "testMethod2": "getTestMethod2", // getTestMethod2 --> testMethod2
    "myProperty": "testMethod3"      // testMethod3 --> myProperty
}

2.3. @JsonPropertyOrder

Аннотация @JsonPropertyOrder используется для указания порядка свойств при сериализации объекта в JSON.

Давайте зададим свой порядок свойств класса MyBean:

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

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

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

2.4. @JsonRawValue

@JsonRawValue используется чтобы Jackson сериализовал свойство так как оно есть.

Пример:

public class RawBean {
    public String name;
 
    @JsonRawValue
    public String json;
}

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

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

2.5. @JsonValue

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

Например для перечислений – мы помечаем метод getName аннотацией @JsonValue таким образом, что элементы перечисления сериализуются через значение свойства name:

public enum TypeEnumWithValue {
    TYPE1(1, "Type A"), TYPE2(2, "Type 2");
 
    private Integer id;
    private String name;
 
    TypeEnumWithValue(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
 
    @JsonValue
    public String getName() {
        return name;
    }
}

Результат:

// "TYPE1" - без @JsonValue
// "Type A" - с @JsonValue

@Test
public void whenSerializingUsingJsonValue_thenCorrect()
  throws JsonParseException, IOException {
  
    String enumAsString = new ObjectMapper()
      .writeValueAsString(TypeEnumWithValue.TYPE1); // "Type A"
 
    assertThat(enumAsString, is("\"Type A\""));
}

2.6. @JsonRootName

Аннотация @JsonRootName используется, если включена вложенность (wrapping), для указания названия корневого элемента.

Вложенность означает, что при сериализации класса User вместо этого:

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

получим вот это:

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

Чтобы изменить название корневого элемента на «user» применим аннотацию @JsonRootName :

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

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

@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"
    }
}

2.7. @JsonSerialize

@JsonSerialize применяется для указания класса, с помощью которого будет производиться сериализация сущности.

Рассмотрим пример – применим аннотацию @JsonSerialize для сериализации свойства eventDate с помощь класса CustomDateSerializer:

public class Event {
    public String name;
 
    @JsonSerialize(using = CustomDateSerializer.class)
    public Date eventDate;
}

Код простого кастомного сериализации Jackson:

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);
    Event event = new Event("party", date);
 
    String result = new ObjectMapper().writeValueAsString(event);
    assertThat(result, containsString(toParse));
}

3. Jackson Deserialization Annotations

Далее рассмотрим аннотации Jackson для десериализации, т.е. для обратного преобразования JSON в Java-объект.

3.1. @JsonCreator

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

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

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

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

Однако, в нашем целевом классе нет поля theName – в нём есть только поле name. При этом мы не хотим менять наш класс. Мы можем указать Jackson, как нужно десериализовывать наш класс, отметив конструктор аннотацией @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 для внедрения своего значения в свойство id:

public class BeanWithInject {
    @JacksonInject
    public int id;
     
    public String name;
}

Обратите внимание, что в JSON нет значения для id, однако в десериализованном объекте оно уже есть:

@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 помечается метод, в который будут передаваться все нераспознанные поля и их значения. Помеченный метод должен иметь определенную сигнатуру. Таким образом можно складывать значение в Map.

Применим @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"
}

Поле name попадёт в свойство name, а поля attr1 и attr2 попадут в метод add, т.к. в классе нет соответствующих свойств attr1, attr2.

Проверка:

@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 не совсем подходят под целевой класс или с данными нужно выполнить какие-то простые проверки или преобразования.

В следующем примере у нас есть метод setTheName() как сеттер свойства name в классе MyBean:

public class MyBean {
    public int id;
    private String name;
 
    @JsonSetter("name")
    public void setTheName(String name) {
        this.name = name;
    }
}

Метод setTheName() будет использоваться для десериализации свойства name:

@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 используется для указания использовать указанный пользовательский десериализатор.

Пометим свойство eventDate аннотацией @JsonDeserialize для десериализации с помощью класса CustomDateDeserializer:

public class Event {
    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");
    Event event = new ObjectMapper()
      .readerFor(Event.class)
      .readValue(json);
     
    assertEquals(
      "20-12-2014 02:30:00", df.format(event.eventDate));
}

4. Jackson Property Inclusion Annotations

4.1. @JsonIgnoreProperties

@JsonIgnoreProperties – one of the most common annotations in Jackson – is used to mark a property or a list of properties to be ignored at the class level.

Let’s go over a quick example ignoring the property id from serialization:

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

And here’s the test making sure the ignore happens:

@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")));
}

4.2. @JsonIgnore

The @JsonIgnore annotation is used to mark a property to be ignored at the field level.

Let’s use @JsonIgnore to ignore the property id from serialization:

public class BeanWithIgnore {
    @JsonIgnore
    public int id;
 
    public String name;
}

And the test making sure that id was successfully ignored:

@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 is used to mark all properties of an annotated type to be ignored.

Let’s use the annotation to mark all properties of type Name to be ignored:

public class User {
    public int id;
    public Name name;
 
    @JsonIgnoreType
    public static class Name {
        public String firstName;
        public String lastName;
    }
}

Here’s the simple test making sure the ignore works correctly:

@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 is used to exclude properties with empty/null/default values.

Let’s look at an example – excluding nulls from serialization:

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

Here’s the full test:

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 is used to override the default semantics of which properties are visible and which are not.

Let’s take a look at how the annotation can be very helpful with a simple example – let’s enable serializing private properties:

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

And the test:

@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. Jackson Polymorphic Type Handling Annotations

Next – let’s take a look at Jackson polymorphic type handling annotations:

  • @JsonTypeInfo is used to indicate details of what type information is included in serialization
  • @JsonSubTypes is used to indicate sub-types of annotated type
  • @JsonTypeName is used to define logical type name to use for annotated class

Let’s look at a more complex example and use all three – @JsonTypeInfo, @JsonSubTypes, and @JsonTypeName – to serialize/deserialize the entity 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;
    }
}

When we do serialization:

@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"
    }
}

And let’s see how that gets unmarshalled to a Zoo instance:

6. Jackson General Annotations

Next – let’s discuss some of Jackson more general annotations.

6.1. @JsonProperty

@JsonProperty is used to indicate the property name in JSON.

Let’s go over the annotation with a simple example – and 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;
    }
}

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 can be used to specify a format when serializing Date/Time values.

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

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

And 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);
    Event event = new Event("party", date);
     
    String result = new ObjectMapper().writeValueAsString(event);
     
    assertThat(result, containsString(toParse));
}

6.3. @JsonUnwrapped

@JsonUnwrapped is used for defining values that should be unwrapped/flattened when serialized/deserialized.

Let’s see exactly how that 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;
    }
}

Let’s now 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")));
}

Here’s how 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 is used to indicate the View in which the property will be included for serialization/deserialization.

An example will show exactly how that works – we’ll use @JsonView to serialize an instance of Item entity.

Let’s start with the views:

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

And now 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 are used to 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;
}

And 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 is used to indicate that Object Identity is to be used when serializing/deserializing values – for instance, to deal with infinite recursion type of problems.

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;
}

And 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 be used during serialization.

Let’s take a look at an example; 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 – as in the following example:

@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 does combine the existing annotations into a simpler, 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.

Let’s use the MixIn annotations to – for example – ignore properties of type User:

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

@JsonIgnoreType
public class MyMixInForIgnoreType {
}

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. Заключение

Мы рассмотрели аннотации Jackson, которые при правильном использовании дают вам гибкость при сериализации/десериализации объектов.

Видео от автора оригинальной статьи:

Код примеров может быть найден по ссылке GitHub project – это maven-проект, и поэтому вам будет его легко импортировать и запустить.

Статья является творческим переводом оригинальной статьи.