More Jackson Annotations – 更多Jackson的注释

最后修改: 2016年 3月 22日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

This article covers some additional annotations that were not covered in the previous article, A Guide to Jackson Annotations – we will go through seven of these.

这篇文章涵盖了前一篇文章《杰克逊注释指南》中没有涉及的一些额外注释–我们将介绍其中的七个注释。

2. @JsonIdentityReference

2.@JsonIdentityReference

@JsonIdentityReference is used for customization of references to objects that will be serialized as object identities instead of full POJOs. It works in collaboration with @JsonIdentityInfo to force usage of object identities in every serialization, different from all-but-the-first-time when @JsonIdentityReference is absent. This couple of annotations is most helpful when dealing with circular dependencies among objects. Please refer to section 4 of the Jackson – Bidirectional Relationship article for more information.

@JsonIdentityReference用于定制对象的引用,这些对象将被序列化为对象身份而不是完整的POJO。它与@JsonIdentityInfo协同工作,在每次序列化中强制使用对象身份,当@JsonIdentityReference不存在时,它不同于所有的首次使用。在处理对象之间的循环依赖关系时,这对注解是最有帮助的。请参考Jackson – Bidirectional Relationship文章的第4节以了解更多信息。

In order to demonstrate the use @JsonIdentityReference, we will define two different bean classes, without and with this annotation.

为了演示@JsonIdentityReference的使用,我们将定义两个不同的Bean类,没有和有这个注解。

The bean without @JsonIdentityReference:

没有@JsonIdentityReference的bean。

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class BeanWithoutIdentityReference {
    private int id;
    private String name;

    // constructor, getters and setters
}

For the bean using @JsonIdentityReference, we choose the id property to be the object identity:

对于使用@JsonIdentityReference的bean,我们选择id属性作为对象身份。

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
@JsonIdentityReference(alwaysAsId = true)
public class BeanWithIdentityReference {
    private int id;
    private String name;
    
    // constructor, getters and setters
}

In the first case, where @JsonIdentityReference is absent, that bean is serialized with full details on its properties:

在第一种情况下,如果没有@JsonIdentityReference,该Bean就会被序列化,并提供其属性的全部细节。

BeanWithoutIdentityReference bean 
  = new BeanWithoutIdentityReference(1, "Bean Without Identity Reference Annotation");
String jsonString = mapper.writeValueAsString(bean);

The output of the serialization above:

上述序列化的输出。

{
    "id": 1,
    "name": "Bean Without Identity Reference Annotation"
}

When @JsonIdentityReference is used, the bean is serialized as a simple identity instead:

@JsonIdentityReference被使用时,Bean被序列化为一个简单的身份。

BeanWithIdentityReference bean 
  = new BeanWithIdentityReference(1, "Bean With Identity Reference Annotation");
String jsonString = mapper.writeValueAsString(bean);
assertEquals("1", jsonString);

3. @JsonAppend

3.@JsonAppend

The @JsonAppend annotation is used to add virtual properties to an object in addition to regular ones when that object is serialized. This is necessary when we want to add supplementary information directly into a JSON string, rather than changing the class definition. For instance, it might be more convenient to insert the version metadata of a bean to the corresponding JSON document than to provide it with an additional property.

@JsonAppend 注解用于在一个对象被序列化时,除了常规属性外,还向该对象添加虚拟属性。当我们想把补充信息直接添加到JSON字符串中,而不是改变类的定义时,这是必要的。例如,在相应的JSON文档中插入Bean的version元数据可能比为它提供一个额外的属性更方便。

Assume we have a bean without @JsonAppend as follows:

假设我们有一个没有@JsonAppend的bean,如下。

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

    // constructor, getters and setters
}

A test will confirm that in the absence of the @JsonAppend annotation, the serialization output does not contain information on the supplementary version property, despite the fact that we attempt to add to the ObjectWriter object:

通过测试可以确认,在没有@JsonAppend注解的情况下,尽管我们试图添加到ObjectWriter对象中,但序列化输出并不包含关于补充version属性的信息。

BeanWithoutAppend bean = new BeanWithoutAppend(2, "Bean Without Append Annotation");
ObjectWriter writer 
  = mapper.writerFor(BeanWithoutAppend.class).withAttribute("version", "1.0");
String jsonString = writer.writeValueAsString(bean);

The serialization output:

串行化输出。

{
    "id": 2,
    "name": "Bean Without Append Annotation"
}

Now, let’s say we have a bean annotated with @JsonAppend:

现在,假设我们有一个用@JsonAppend注释的bean。

@JsonAppend(attrs = { 
  @JsonAppend.Attr(value = "version") 
})
public class BeanWithAppend {
    private int id;
    private String name;

    // constructor, getters and setters
}

A similar test to the previous one will verify that when the @JsonAppend annotation is applied, the supplementary property is included after serialization:

一个与前面类似的测试将验证当应用@JsonAppend注解时,补充属性在序列化后被包含。

BeanWithAppend bean = new BeanWithAppend(2, "Bean With Append Annotation");
ObjectWriter writer 
  = mapper.writerFor(BeanWithAppend.class).withAttribute("version", "1.0");
String jsonString = writer.writeValueAsString(bean);

The output of that serialization shows that the version property has been added:

该序列化的输出显示,version属性已被添加。

{
    "id": 2,
    "name": "Bean With Append Annotation",
    "version": "1.0"
}

4. @JsonNaming

4.@JsonNaming

The @JsonNaming annotation is used to choose the naming strategies for properties in serialization, overriding the default. Using the value element, we can specify any strategy, including custom ones.

@JsonNaming 注解用于选择序列化中属性的命名策略,覆盖默认值。使用value元素,我们可以指定任何策略,包括自定义策略。

In addition to the default, which is LOWER_CAMEL_CASE (e.g. lowerCamelCase), Jackson library provides us with four other built-in property naming strategies for convenience:

除了默认的LOWER_CAMEL_CASE(例如lowerCamelCase)之外,Jackson库还为我们提供了其他四种内置的属性命名策略,以方便我们使用。

  • KEBAB_CASE: Name elements are separated by hyphens, e.g. kebab-case.
  • LOWER_CASE: All letters are lowercase with no separators, e.g. lowercase.
  • SNAKE_CASE: All letters are lowercase with underscores as separators between name elements, e.g. snake_case.
  • UPPER_CAMEL_CASE: All name elements, including the first one, start with a capitalized letter, followed by lowercase ones and there are no separators, e.g. UpperCamelCase.

This example will illustrate the way to serialize properties using snake case names, where a property named beanName is serialized as bean_name.

这个例子将说明使用蛇形例名来序列化属性的方法,一个名为beanName的属性被序列化为bean_name.

Given a bean definition:

给定一个Bean的定义。

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class NamingBean {
    private int id;
    private String beanName;

    // constructor, getters and setters
}

The test below demonstrates that the specified naming rule works as required:

下面的测试表明,指定的命名规则按要求工作。

NamingBean bean = new NamingBean(3, "Naming Bean");
String jsonString = mapper.writeValueAsString(bean);        
assertThat(jsonString, containsString("bean_name"));

The jsonString variable contains following data:

jsonString变量包含以下数据。

{
    "id": 3,
    "bean_name": "Naming Bean"
}

5. @JsonPropertyDescription

5.@JsonPropertyDescription

The Jackson library is able to create JSON schemas for Java types with the help of a separate module called JSON Schema. The schema is useful when we want to specify expected output when serializing Java objects, or to validate a JSON document before deserialization.

Jackson库能够在一个名为JSON Schema的独立模块的帮助下为Java类型创建JSON模式。当我们想在序列化Java对象时指定预期的输出,或者在反序列化之前验证JSON文档时,该模式非常有用。

The @JsonPropertyDescription annotation allows a human readable description to be added to the created JSON schema by providing the description field.

@JsonPropertyDescription注解允许通过提供description字段将人类可读的描述添加到创建的JSON模式中。

This section makes use of the bean declared below to demonstrate the capabilities of @JsonPropertyDescription:

本节利用下面声明的Bean来演示@JsonPropertyDescription的功能。

public class PropertyDescriptionBean {
    private int id;
    @JsonPropertyDescription("This is a description of the name property")
    private String name;

    // getters and setters
}

The method for generating a JSON schema with the addition of the description field is shown below:

生成一个添加了描述字段的JSON模式的方法如下。

SchemaFactoryWrapper wrapper = new SchemaFactoryWrapper();
mapper.acceptJsonFormatVisitor(PropertyDescriptionBean.class, wrapper);
JsonSchema jsonSchema = wrapper.finalSchema();
String jsonString = mapper.writeValueAsString(jsonSchema);
assertThat(jsonString, containsString("This is a description of the name property"));

As we can see, the generation of JSON schema was successful:

我们可以看到,JSON模式的生成是成功的。

{
    "type": "object",
    "id": "urn:jsonschema:com:baeldung:jackson:annotation:extra:PropertyDescriptionBean",
    "properties": 
    {
        "name": 
        {
            "type": "string",
            "description": "This is a description of the name property"
        },

        "id": 
        {
            "type": "integer"
        }
    }
}

6. @JsonPOJOBuilder

6.@JsonPOJOBuilder

The @JsonPOJOBuilder annotation is used to configure a builder class to customize deserialization of a JSON document to recover POJOs when the naming convention is different from the default.

@JsonPOJOBuilder注解用于配置一个构建器类,以定制JSON文档的反序列化,以便在命名规则与默认不同时恢复POJO。

Suppose we need to deserialize the following JSON string:

假设我们需要对以下JSON字符串进行反序列化。

{
    "id": 5,
    "name": "POJO Builder Bean"
}

That JSON source will be used to create an instance of the POJOBuilderBean:

该JSON源将被用来创建一个POJOBuilderBean的实例。

@JsonDeserialize(builder = BeanBuilder.class)
public class POJOBuilderBean {
    private int identity;
    private String beanName;

    // constructor, getters and setters
}

The names of the bean’s properties are different from those of the fields in JSON string. This is where @JsonPOJOBuilder comes to the rescue.

Bean的属性名称与JSON字符串中的字段不同。这时,@JsonPOJOBuilder就会来帮忙。

The @JsonPOJOBuilder annotation is accompanied by two properties:

@JsonPOJOBuilder注解有两个属性。

  • buildMethodName: The name of the no-arg method used to instantiate the expected bean after binding JSON fields to that bean’s properties. The default name is build.
  • withPrefix: The name prefix for auto-detection of matching between the JSON and bean’s properties. The default prefix is with.

This example makes use of the BeanBuilder class below, which is used on POJOBuilderBean:

这个例子利用了下面的BeanBuilder类,它被用于POJOBuilderBean

@JsonPOJOBuilder(buildMethodName = "createBean", withPrefix = "construct")
public class BeanBuilder {
    private int idValue;
    private String nameValue;

    public BeanBuilder constructId(int id) {
        idValue = id;
        return this;
    }

    public BeanBuilder constructName(String name) {
        nameValue = name;
        return this;
    }

    public POJOBuilderBean createBean() {
        return new POJOBuilderBean(idValue, nameValue);
    }
}

In the code above, we have configured the @JsonPOJOBuilder to use a build method called createBean and the construct prefix for matching properties.

在上面的代码中,我们将@JsonPOJOBuilder配置为使用一个名为createBean的构建方法和construct前缀来匹配属性。

The application of @JsonPOJOBuilder to a bean is described and tested as follows:

@JsonPOJOBuilder在bean上的应用描述和测试如下。

String jsonString = "{\"id\":5,\"name\":\"POJO Builder Bean\"}";
POJOBuilderBean bean = mapper.readValue(jsonString, POJOBuilderBean.class);

assertEquals(5, bean.getIdentity());
assertEquals("POJO Builder Bean", bean.getBeanName());

The result shows that a new data object has been successfully re-created from a JSON source in despite a mismatch in properties’ names.

结果显示,尽管属性名称不匹配,但已经成功地从JSON源重新创建了一个新的数据对象。

7. @JsonTypeId

7.@JsonTypeId

The @JsonTypeId annotation is used to indicate that the annotated property should be serialized as the type id when including polymorphic type information, rather than as a regular property. That polymorphic metadata is used during deserialization to recreate objects of the same subtypes as they were before serialization, rather than of the declared supertypes.

@JsonTypeId注解用来表示当包括多态类型信息时,被注解的属性应该被序列化为类型id,而不是作为一个普通的属性。该多态元数据在反序列化过程中被用来重新创建与序列化前相同的子类型的对象,而不是被声明的超类型的对象。

For more information on Jackson’s handling of inheritance, see section 2 of the Inheritance in Jackson.

关于Jackson处理继承的更多信息,请参见Jackson中的继承第2节。

Let’s say we have a bean class definition as follows:

假设我们有一个Bean的定义,如下所示。

public class TypeIdBean {
    private int id;
    @JsonTypeId
    private String name;

    // constructor, getters and setters
}

The following test validates that @JsonTypeId works as it is meant to:

下面的测试验证了@JsonTypeId的工作原理。

mapper.enableDefaultTyping(DefaultTyping.NON_FINAL);
TypeIdBean bean = new TypeIdBean(6, "Type Id Bean");
String jsonString = mapper.writeValueAsString(bean);
        
assertThat(jsonString, containsString("Type Id Bean"));

The serialization process’ output:

串行化过程的输出。

[
    "Type Id Bean",
    {
        "id": 6
    }
]

8. @JsonTypeIdResolver

8.@JsonTypeIdResolver

The @JsonTypeIdResolver annotation is used to signify a custom type identity handler in serialization and deserialization. That handler is responsible for conversion between Java types and type id included in a JSON document.

@JsonTypeIdResolver 注解用于表示序列化和反序列化中的自定义类型标识处理器。该处理程序负责Java类型和JSON文档中的类型标识之间的转换。

Suppose that we want to embed type information in a JSON string when dealing with the following class hierarchy.

假设我们在处理以下类的层次结构时,想把类型信息嵌入JSON字符串中。

The AbstractBean superclass:

AbstractBean的超类。

@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME, 
  include = JsonTypeInfo.As.PROPERTY, 
  property = "@type"
)
@JsonTypeIdResolver(BeanIdResolver.class)
public class AbstractBean {
    private int id;

    protected AbstractBean(int id) {
        this.id = id;
    }

    // no-arg constructor, getter and setter
}

The FirstBean subclass:

FirstBean子类。

public class FirstBean extends AbstractBean {
    String firstName;

    public FirstBean(int id, String name) {
        super(id);
        setFirstName(name);
    }

    // no-arg constructor, getter and setter
}

The LastBean subclass:

LastBean子类。

public class LastBean extends AbstractBean {
    String lastName;

    public LastBean(int id, String name) {
        super(id);
        setLastName(name);
    }

    // no-arg constructor, getter and setter
}

Instances of those classes are used to populate a BeanContainer object:

这些类的实例被用来填充一个BeanContainer对象。

public class BeanContainer {
    private List<AbstractBean> beans;

    // getter and setter
}

We can see that the AbstractBean class is annotated with @JsonTypeIdResolver, indicating that it uses a custom TypeIdResolver to decide how to include subtype information in serialization and how to make use of that metadata the other way round.

我们可以看到,AbstractBean类被注解为@JsonTypeIdResolver,表明它使用自定义的TypeIdResolver来决定如何在序列化中包含子类型信息以及如何反过来利用该元数据。

Here is the resolver class to handle inclusion of type information:

这里是用于处理包含类型信息的解析器类。

public class BeanIdResolver extends TypeIdResolverBase {
    
    private JavaType superType;

    @Override
    public void init(JavaType baseType) {
        superType = baseType;
    }

    @Override
    public Id getMechanism() {
        return Id.NAME;
    }

    @Override
    public String idFromValue(Object obj) {
        return idFromValueAndType(obj, obj.getClass());
    }

    @Override
    public String idFromValueAndType(Object obj, Class<?> subType) {
        String typeId = null;
        switch (subType.getSimpleName()) {
        case "FirstBean":
            typeId = "bean1";
            break;
        case "LastBean":
            typeId = "bean2";
        }
        return typeId;
    }

    @Override
    public JavaType typeFromId(DatabindContext context, String id) {
        Class<?> subType = null;
        switch (id) {
        case "bean1":
            subType = FirstBean.class;
            break;
        case "bean2":
            subType = LastBean.class;
        }
        return context.constructSpecializedType(superType, subType);
    }
}

The two most notable methods are idFromValueAndType and typeFromId, with the former telling the way to include type information when serializing POJOs and the latter determining the subtypes of re-created objects using that metadata.

两个最值得注意的方法是idFromValueAndTypetypeFromId,前者告诉在序列化POJO时包含类型信息的方式,后者则使用该元数据确定重新创建的对象的子类型。

In order to make sure that both serialization and deserialization work well, let’s write a test to validate the complete progress.

为了确保序列化和反序列化工作顺利,让我们写一个测试来验证完整的进度。

First, we need to instantiate a bean container and bean classes, then populate that container with bean instances:

首先,我们需要实例化一个Bean容器和Bean类,然后用Bean实例填充该容器。

FirstBean bean1 = new FirstBean(1, "Bean 1");
LastBean bean2 = new LastBean(2, "Bean 2");

List<AbstractBean> beans = new ArrayList<>();
beans.add(bean1);
beans.add(bean2);

BeanContainer serializedContainer = new BeanContainer();
serializedContainer.setBeans(beans);

Next, the BeanContainer object is serialized and we confirm that the resulting string contains type information:

接下来,BeanContainer对象被序列化,我们确认产生的字符串包含类型信息。

String jsonString = mapper.writeValueAsString(serializedContainer);
assertThat(jsonString, containsString("bean1"));
assertThat(jsonString, containsString("bean2"));

The output of serialization is shown below:

序列化的输出如下所示。

{
    "beans": 
    [
        {
            "@type": "bean1",
            "id": 1,
            "firstName": "Bean 1"
        },

        {
            "@type": "bean2",
            "id": 2,
            "lastName": "Bean 2"
        }
    ]
}

That JSON structure will be used to re-create objects of the same subtypes as before serialization. Here are the implementation steps for deserialization:

该JSON结构将被用来重新创建与序列化前相同子类型的对象。下面是反序列化的实现步骤。

BeanContainer deserializedContainer = mapper.readValue(jsonString, BeanContainer.class);
List<AbstractBean> beanList = deserializedContainer.getBeans();
assertThat(beanList.get(0), instanceOf(FirstBean.class));
assertThat(beanList.get(1), instanceOf(LastBean.class));

9. Conclusion

9.结论

This tutorial has explained several less-common Jackson annotations in detail. The implementation of these examples and code snippets can be found in a GitHub project.

本教程已经详细解释了几个不太常见的Jackson注释。这些例子和代码片段的实现可以在一个GitHub项目中找到。