1. 概述

本教程,我们将深入学习Jackson 各种注解的使用方法。

内容包括基本用法,如何创建自定义注解,以及如何禁用它们。

2. Jackson 序列化例子

首先,我们来看看关于序列化的注解。

2.1. @JsonAnyGetter

@JsonAnyGetter注解允许将Map中key/value直接映射到JSON上,非常灵活。

例如,ExtendableBean实体类有一个name属性,还有一些扩展属性存放在properties 这个map中:

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

当我们序列化后,map中的扩展属性不会挂在properties节点下,而是直接映射到JSON根节点下:

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

@JsonAnyGetter()有个enable参数,设为false时,可以禁用此功能。此时序列化后map中的属性会挂在properties节点下。

2.2. @JsonGetter

@JsonGetter 注解可以替代@JsonProperty注解,将一个方法标记为getter方法。

下面例子中,我们指定了getTheName()作为MyBean实体类name属性的getter方法:

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注解可以要求Jackson按原样序列化属性

例如下面实体类的json字段是一个JSON字符串。我们想让其作为内嵌JSON原样输出,而不是一个字符串,则可以使用@JsonRawValue注解:

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 修饰的字段(或getter方法)为该类序列化后的结果。

例如,在一个枚举中,我们用@JsonValue注解了getName,那么整个序列化结果就是name的值:

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 注解用来指定root wrapper的名字。注意,只有当WRAP_ROOT_VALUE开启时,此注解才生效。

比如,我们不想把User序列化下面这样:

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

而是想添加一个root wrapper,挂载在"User"节点下:

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

我们将用@JsonRootName注解指明root wrapper的名字:

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

默认情况下,如果不指定value,则默认使用类名 —— 这里把UserWithRoot作为root wrapper的名字。

@Test
    public void whenSerializingUsingJsonRootName_thenCorrect()
      throws JsonProcessingException {
     
        UserWithRoot user = new User(1, "John");
    
        ObjectMapper mapper = new ObjectMapper();
        // 只有当`WRAP_ROOT_VALUE`开启时,此注解才生效。
        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,新增了一个可选参数namespace可用于XML等数据格式。如果添加它,它将成为完全限定名称的一部分:

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

如果使用XmlMapper将其序列化为XML,输出结果为:

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

2.7. @JsonSerialize

使用@JsonSerialize注解,自定义序列化器。

来看个例子,我们用@JsonSerialize指定通过CustomDateSerializer来序列化eventDate属性。

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

下面是自定义序列化CustomDateSerializer的定义:

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. Jackson 反序列化注解

接下来,让我们继续探索Jackson关于反序列化的注解。

3.1. @JsonCreator

@JsonCreator注解指定对象反序列化时,使用的构造函数或者工厂方法。

当我们需要解析的JSON和目标实体类不匹配,需要做一些特殊操作时,这个注解就很有用。另外,Jackson默认会调用对象的无参构造函数,但是如果我们定义了有参构造函数,但没有提供无参构造函数时,Jackson就会报错,此时也可以用到@JsonCreator来解决这个问题。

假设我们要反序列化下面的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 indicates that a property will get its value from the injection and not from the JSON data.

In the following example, we use @JacksonInject to inject the property id:

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

Here’s how it works:

@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 allows us the flexibility of using a Map as standard properties. On deserialization, the properties from JSON will simply be added to the map.

First, we’ll use @JsonAnySetter to deserialize the entity ExtendableBean:

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

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

This is the JSON we need to deserialize:

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

Then here’s how it all ties in together:

@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 is an alternative to @JsonProperty that marks the method as a setter method.

This is incredibly useful when we need to read some JSON data, but the target entity class doesn’t exactly match that data, and so we need to tune the process to make it fit.

In the following example, we’ll specify the method s_etTheName()_ as the setter of the name property in our MyBean entity:

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

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

Now when we need to unmarshall some JSON data, this works perfectly well:

@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 indicates the use of a custom deserializer.

First, we’ll use @JsonDeserialize to deserialize the eventDate property with the CustomDateDeserializer:

public class EventWithSerializer {
    public String name;

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

Here’s the custom deserializer:

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

Next here’s the back-to-back test:

@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

The @JsonAlias defines one or more alternative names for a property during deserialization.

Let’s see how this annotation works with a quick example:

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

Here we have a POJO, and we want to deserialize JSON with values such as fName, f_name, and firstName into the firstName variable of the POJO.

Below is a test making sure this annotation works as expected:

@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. Jackson 包含/排除某个属性

4.1. @JsonIgnoreProperties

@JsonIgnoreProperties is a class-level annotation that marks a property or a list of properties that Jackson will ignore.

Let’s look at a quick example ignoring the property id from serialization:

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

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

To ignore any unknown properties in JSON input without exception, we can set ignoreUnknown=true of @JsonIgnoreProperties annotation.

4.2. @JsonIgnore

In contrast, 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;
}

Then we’ll test to make 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 marks all properties of an annotated type to be ignored.

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

We can also test to ensure 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

We can use @JsonInclude 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 can override the default semantics of which properties are visible and which are not.

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

Then 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 多态类型处理

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

  • @JsonTypeInfo – indicates details of what type information to include in serialization
  • @JsonSubTypes – indicates sub-types of the annotated type
  • @JsonTypeName – defines a logical type name to use for annotated class

Let’s examine 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"
    }
}

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 其他通用注解

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. 自定义 Jackson 注解

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. 禁用 Jackson 注解

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注释,只是简单介绍了正确使用它们可以获得的灵活性。