1. 概述

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

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

2. Jackson 序列化例子

本节,讲解关于序列化的注解。

2.1. @JsonAnyGetter

@JsonAnyGetter 注解主要用于动态字段,我们可以将一些不确定的字段维护在一个Map对象中,并像标准属性一样序列化,非常灵活。

例如,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节点下。

反序列化时与之对应的有一个 @JsonAnySetter 注解,在后面的章节中会提供。

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 注解后,属性的值将从我们注入的值中获取,而不是从JSON中。

下面我们演示使用 @JacksonInject 注入 id字段:

    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);
        // 这里bean.id的值来自inject
        assertEquals(1, bean.id);
    }

3.3. @JsonAnySetter

使用 @JsonAnySetter 时我们可以利用 Map 灵活性,序列化的时候将所有JSON字段都添加到map中。

需要反序列化的 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 的替代品,它将方法标记为 setter 方法。

当我们需要读取的JSON 数据和目标entity字段名称不一样时,这个注解就派上用场了。

下面例子中,我们指定 setTheName()方法作为name字段的setter方法

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

    @JsonSetter("name")
    public void setTheName(String name) {
        this.name = 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 字段使用 CustomDateDeserializer来实现反序列化:

public class EventWithSerializer {
    public String name;

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

CustomDateDeserializer 定义如下:

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

本例中JSON 中的含有 “fName”、“f_name” 或 firstName 的字段都可以被反序列化到 POJO的firstName属性中。

测试代码:

@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 是一个 class 级的注解,用于标记Jackson需要忽略的属性列表。

让我们看看如何忽略 "id" 字段:

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

现在测试看看id字段序列化时是否被忽略:

@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 输入的未知字段而不抛异常,可设置 @JsonIgnoreProperties 注解中的ignoreUnknown=true

4.2. @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"));
    // firstName和lastName将被忽略
    assertThat(result, not(containsString("name")));
    assertThat(result, not(containsString("John")));
}

4.4. @JsonInclude

使用 @JsonInclude 可用于排除属性值为 空/null的字段。

如下,我们定义只序列化非NULL的字段:

@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 @JsonIncludeProperties

@JsonIncludeProperties 是Jackson呼声最多的注解之一,于Jackson 2.12版本中引入。 用于标记序列化/反序列化过程中需要包含的属性列表。

例如下面序列化时,输出的结果只包含 name 字段:

@JsonIncludeProperties({ "name" })
public class BeanWithInclude {
    public int id;
    public String name;
}

测试验证

@Test
public void whenSerializingUsingJsonIncludeProperties_thenCorrect() throws JsonProcessingException {
    final BeanWithInclude bean = new BeanWithInclude(1, "My bean");
    final String result = new ObjectMapper().writeValueAsString(bean);
    assertThat(result, containsString("My bean"));
    assertThat(result, not(containsString("id")));
    assertThat(result, containsString("name"));
}

4.6. @JsonAutoDetect

默认情况下,Jackson 只对具有 public 的字段或带有getters/setters的字段进行序列化和反序列化。使用 @JsonAutoDetect 可以覆盖这种行为,指定字段、方法的可见性规则。

下面例子,我们允许对private属性进行序列化:

@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. Jackson 多态类型处理

下面我们学习 Jackson 对多态类型处理的支持:

  • @JsonTypeInfo – 指示序列化中包含的类型信息的详细信息
  • @JsonSubTypes – 用于感知被注解类有那些子类
  • @JsonTypeName – 用于指定被注解类的逻辑名称

下面我们来看一个复杂的示例,序列化/反序列化Zoo实体类,用到@JsonTypeInfo、@JsonSubTypes以及@JsonTypeName 这三个注解。

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

Zoo(实际类型为Dog)序列化结果:

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

下面演示反序列化,JSON输入如下:

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

测试代码:

@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);
    // 反序列化后对象类型应该是Zoo的子类: Cat
    assertEquals(Zoo.Cat.class, zoo.animal.getClass());
}

6. Jackson 其他通用注解

6.1. @JsonProperty

@JsonProperty 注解用来将JSON字符串中的属性名和Java类中的属性名对应起来。

例如下面 name 字段的getter和setter方法名和JSON中的不一样,此时就可以用到 @JsonProperty 注解。

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

验证测试:

@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 注解

** @JsonFormat 注解用于指定序列化Date/Time类型字段的格式。**

在下面的示例中,我们使用 @JsonFormat 来控制 eventDate 字段的格式:

public class EventWithFormat {
    public String name;

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

测试代码:

@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 注解用来偏平化、展开嵌套字段。

例如我们将 name 字段进行进行 unwrapped 操作:

public class UnwrappedUser {
    public int id;

    @JsonUnwrapped
    public Name name;

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

下面我们来试试序列化后是什么样子

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

我们可以看到输出的结果,没有name字段,其下包裹的字段被提到了外层

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

6.4. @JsonView 注解

@JsonView 注解可用于动态控制需要包含的序列化/反序列字段

下面我们将使用 @JsonView 注解来序列化 Item 实体。

首先定义 View:

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

然后定义 Item 实体,并添加 view 注解:

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

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

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

然后测试:

@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"));
    // 结果将不包含 ownerName 字段
    assertThat(result, not(containsString("John")));
}

6.5. @JsonManagedReference注解, @JsonBackReference注解

@JsonManagedReference 和 @JsonBackReference 注释可处理parent/child依赖关系,解决循环引用的问题。

下面例子中,UserWithRefItemWithRef 存在相互引用 。如果不加注解,会抛 StackOverflowError 的异常。

ItemWithRef 定义:

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

    @JsonManagedReference
    public UserWithRef owner;
}

UserWithRef 定义:

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

    @JsonBackReference
    public List<ItemWithRef> userItems;
}

测试代码:

@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 主要作用是将对象之间的引用表示为唯一的标识符,而不是完整的对象,通常用于解决循环引用问题。

下面例子中,ItemWithIdentity 实体和 UserWithIdentity 之间有双向引用关系:

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

UserWithIdentity 实体:

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

下面就不会出现循环引用的问题了:

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

下面是完整的序列化结果:

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

6.7. @JsonFilter 注解

@JsonFilter 注解指定序列化时要使用的过滤器。

首先,@JsonFilter 指定过滤器的名称:

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

下面是完成测试代码,我们定义一个名为myFilter的过滤器,序列化时将排除除 name 之外的其他属性:

@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 注解

可以利用 @JacksonAnnotationsInside 创建自定义Jackson注解:

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

我们创建了名为CustomAnnotation的注解,现在应用到我们的实体类上试试:

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

可以看到我们将多个现有的Jackson组合到一个注解上了,更简洁:

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

序列化输出结果:

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

8. Mixin - 混合注解

Mixin 即混合的意思。使用Jackson MixIn 注解功能,可以在不修改实际类的基础上添加注解,例如一些第三方库我们没法修改源码为其添加注解。

例如,下面的例子。通过我们希望忽略Item中User类型的字段:

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

直接的做法是,修改User类定义,添加 @JsonIgnoreType 注解

@JsonIgnoreType
class User {
    ...
}

但假设,现在没法直接修改User类,那就可以通过Mixin类实现:

@JsonIgnoreType
public class MyMixInForIgnoreType {}

使用:

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

    // 混合前,包含onwer
    String result = new ObjectMapper().writeValueAsString(item);
    assertThat(result, containsString("owner"));

    // 混合后,不包含onwer
    ObjectMapper mapper = new ObjectMapper();
    // 关键方法
    mapper.addMixIn(User.class, MyMixInForIgnoreType.class);

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

上面,通过ObjectMapper的addMixIn方法,添加我们要混入的类。addMixIn的定义:

/**
 * @param target - 被混入的目标类
 * @param mixinSource - 要混入注解的来源类
 *
/
ObjectMapper addMixIn(Class<?> target, Class<?> mixinSource)

9. 禁用 Jackson 注解

如何禁用所有Jackson注解?我们可以通过设置 MapperFeature.USE_ANNOTATIONS 参数实现。例如我们下面的实体用到注解:

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

禁用前输出会结果:

{"id":1}

下面禁用注解:

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

禁用后结果:

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

10. 总结

在本文中,我们研究了Jackson注释,只是简单介绍了正确使用它们可以获得的灵活性。

本文中的所有示例代码,可从GitHub上获取。