更多 Jackson 註解

Data,Jackson
Remote
0
12:36 AM · Dec 01 ,2025

1. 概述

本文檔將介紹一些在先前文章《Jackson 註解指南》中未涵蓋的補充説明,我們將深入探討其中的七個。

2.

用於自定義對象引用,這些引用將作為對象標識進行序列化,而不是完整的 POJO。它與 協同工作,強制在每次序列化中使用對象標識,與 不存在的時機不同。這對處理對象之間的循環依賴關係特別有用。請參閲 Jackson – 雙向關係文章的第 4 節以獲取更多信息。

為了演示 的使用,我們將定義兩個不同的 Bean 類,一個帶有,一個不帶有此註解。

不帶 的 Bean:

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

    // constructor, getters and setters
}

對於使用 的 Bean,我們選擇 屬性作為對象標識:

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

在第一個案例中,當 不存在時,該 Bean 將使用其屬性的完整詳細信息進行序列化:

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

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.

Assume we have a bean without @JsonAppend as follows:

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:

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(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:

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:

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

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.

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:

  • 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.

Given a bean definition:

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

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

5. @JsonPropertyDescription

Jackson 庫能夠通過一個名為 JSON Schema 的模塊,為 Java 類型創建 JSON 模式。該模式在我們需要指定 Java 對象序列化的預期輸出,或在反序列化之前驗證 JSON 文檔時非常有用。

@JsonPropertyDescription 註解允許通過提供 description 字段,為創建的 JSON 模式添加人類可讀的描述。

本節利用下面的 Bean 來演示 @JsonPropertyDescription 的功能:

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

    // getters and setters
}

生成帶有 description 字段的 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"));

正如我們所看到的,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

@JsonPOJOBuilder 註解用於配置構建器類以自定義從 JSON 文檔進行反序列化以恢復 POJO,即使命名約定與默認值不同。

假設我們需要反序列化以下 JSON 字符串:

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

該 JSON 源將用於創建 POJOBuilderBean 實例:

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

    // constructor, getters and setters
}

bean 的屬性名稱與 JSON 字符串中的字段名稱不同。這就是@JsonPOJOBuilder 派上用場的地方。

@JsonPOJOBuilder 註解伴隨兩個屬性:

  • buildMethodName: 用於實例化期望的 bean 的無參數方法名稱,在將 JSON 字段綁定到該 bean 的屬性之後。默認名稱為 build
  • withPrefix: 用於自動檢測 JSON 和 bean 屬性之間匹配的名稱前綴。默認前綴為 with

此示例使用了下面的 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);
    }
}

在上面的代碼中,我們配置了@JsonPOJOBuilder 使用名為 createBean 的構建方法和用於匹配屬性的 construct 前綴。

@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());

結果表明,儘管屬性名稱不匹配,但已成功從 JSON 源重新創建了一個新的數據對象。

7. @JsonTypeId

@JsonTypeId 註解用於指示被註解的屬性在包含多態類型信息時應作為類型 ID 進行序列化,而不是作為常規屬性。該多態元數據在反序列化過程中用於重構與序列化之前相同類型的子類型,而不是聲明的超類型。

有關 Jackson 處理繼承的信息,請參閲“Jackson 中的繼承”部分的第 2 節。

假設我們有一個 bean 類定義如下:

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

    // constructor, getters and setters
}

以下測試驗證@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"));

序列化過程的輸出:

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

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.

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

The AbstractBean superclass:

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

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:

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:

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.

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.

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:

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:

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:

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. 結論

本教程詳細介紹了多種不常見的Jackson註解。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.