1. 概述
本文將介紹一些在先前文章《Jackson 註解指南》中未涵蓋的補充註釋,我們將深入探討其中的七個。
2. <em @JsonIdentityReference/>
@JsonIdentityReference 用於自定義對象引用,這些引用將被序列化為對象身份,而不是完整的POJO。它與 @JsonIdentityInfo 協同工作,強制在每次序列化中使用對象身份,與@JsonIdentityReference 缺失時不同。 這種組合註解在處理對象之間的循環依賴關係時特別有用。 請參閲 Jackson – 雙向關係文章的第 4 節以獲取更多信息。
為了演示 @JsonIdentityReference 的使用,我們將定義兩個不同的 Bean 類,一個帶有,一個不帶有此註解。
不帶 @JsonIdentityReference 的 Bean:
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class BeanWithoutIdentityReference {
private int id;
private String name;
// constructor, getters and setters
}對於使用 @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
}在第一種情況下,當不存在 @JsonIdentityReference 標記時,該 Bean 將以包含其所有屬性的完整細節進行序列化:
BeanWithoutIdentityReference bean
= new BeanWithoutIdentityReference(1, "Bean Without Identity Reference Annotation");
String jsonString = mapper.writeValueAsString(bean);上述序列化的輸出:
{
"id": 1,
"name": "Bean Without Identity Reference Annotation"
}當使用 <em @JsonIdentityReference> 時,Bean 會被序列化為簡單的身份:
BeanWithIdentityReference bean
= new BeanWithIdentityReference(1, "Bean With Identity Reference Annotation");
String jsonString = mapper.writeValueAsString(bean);
assertEquals("1", jsonString);3. <em @JsonAppend>
段落標題:The <em @JsonAppend> 註解用於在對象序列化時,除了常規屬性外,還向對象添加虛擬屬性。當我們需要將補充信息直接插入到 JSON 字符串中,而不是修改類定義時,這非常有用。例如,將 Bean 的 <em version> 元數據插入到相應的 JSON 文檔中,可能比提供一個額外的屬性更方便。
段落標題:假設我們有一個沒有 <em @JsonAppend> 註解的 Bean,如下所示:
public class BeanWithoutAppend {
private int id;
private String name;
// constructor, getters and setters
}一個測試將確認,在缺少 @JsonAppend 註解的情況下,序列化輸出不包含關於補充 版本 屬性的信息,儘管我們嘗試向 對象寫入器 添加信息:
BeanWithoutAppend bean = new BeanWithoutAppend(2, "Bean Without Append Annotation");
ObjectWriter writer
= mapper.writerFor(BeanWithoutAppend.class).withAttribute("version", "1.0");
String jsonString = writer.writeValueAsString(bean);序列化輸出:
{
"id": 2,
"name": "Bean Without Append Annotation"
}現在,假設我們有一個用 @JsonAppend 註解標記的 Bean:
@JsonAppend(attrs = {
@JsonAppend.Attr(value = "version")
})
public class BeanWithAppend {
private int id;
private String name;
// constructor, getters and setters
}與之前測試類似的測試將驗證當應用了 @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);該序列化輸出顯示,version 屬性已被添加:
{
"id": 2,
"name": "Bean With Append Annotation",
"version": "1.0"
}4.
The > 標註用於在序列化過程中選擇屬性的命名策略,覆蓋默認設置。通過使用 > 元素,我們可以指定任何策略,包括自定義策略。
除了默認設置,即 (例如 ),Jackson 庫還提供了四個內置的屬性命名策略,以供便捷使用:
- :元素之間用連字符分隔,例如 。
- :所有字母均為小寫,沒有分隔符,例如 。
- :所有字母均為小寫,使用下劃線作為元素之間分隔符,例如 。
- :所有元素(包括第一個)都以大寫字母開頭,後跟小寫字母,並且沒有分隔符,例如 。
此示例將演示如何使用 snake case 命名序列化屬性,其中一個名為 的屬性將被序列化為 。
給定一個 bean 定義:
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class NamingBean {
private int id;
private String beanName;
// constructor, getters and setters
}以下測試表明,指定的命名規則按要求執行:
NamingBean bean = new NamingBean(3, "Naming Bean");
String jsonString = mapper.writeValueAsString(bean);
assertThat(jsonString, containsString("bean_name"));jsonString 變量包含以下數據:
{
"id": 3,
"bean_name": "Naming Bean"
}5. <em @JsonPropertyDescription
Jackson 庫可以通過名為 JSON Schema 的模塊,為 Java 類型創建 JSON 模式。該模式在我們需要指定 Java 對象序列化的預期輸出,或在反序列化之前驗證 JSON 文檔時非常有用。
<em @JsonPropertyDescription 註解允許通過提供 description 字段,為創建的 JSON 模式添加人類可讀的描述。
本節利用下面的 Bean 來演示 <em @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 的過程,當命名約定與默認約定不同時,可以恢復 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
}豆子的屬性名稱與 JSON 字符串中的字段名稱不同。這時,@JsonPOJOBuilder 就能派上用場。
註解 @JsonPOJOBuilder 伴隨着兩個屬性:
- buildMethodName:用於在將 JSON 字段綁定到該豆子的屬性後實例化期望的豆子的無參數方法的名稱。默認名稱為 build。
- withPrefix:用於自動檢測 JSON 與豆子屬性之間匹配的前綴。默認前綴為 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. <em @JsonTypeId>
The <em @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
@JsonTypeIdResolver 註解用於指示序列化和反序列化過程中的自定義類型身份處理程序。該處理程序負責將 Java 類型與 JSON 文檔中包含的類型 ID 之間進行轉換。
假設我們處理以下類層次結構時,希望在 JSON 字符串中嵌入類型信息。
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
}FirstBean 子類:
public class FirstBean extends AbstractBean {
String firstName;
public FirstBean(int id, String name) {
super(id);
setFirstName(name);
}
// no-arg constructor, getter and setter
}LastBean 子類:
public class LastBean extends AbstractBean {
String lastName;
public LastBean(int id, String name) {
super(id);
setLastName(name);
}
// no-arg constructor, getter and setter
}實例這些類被用於填充一個 BeanContainer 對象:
public class BeanContainer {
private List<AbstractBean> beans;
// getter and setter
}我們能看到,AbstractBean 類使用了 @JsonTypeIdResolver 註解,表明它使用自定義的 TypeIdResolver 來決定在序列化過程中如何包含子類型信息,以及如何利用這些元數據進行反序列化。
以下是處理包含類型信息的分解析器類:
// 此類用於解析類型信息,以便在序列化和反序列化過程中正確處理子類型。
// 它允許指定如何存儲和檢索子類型 ID,從而提高序列化的效率和可讀性。
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);
}
}以下兩種方法最為突出,即 idFromValueAndType 和 typeFromId。前者規定了在序列化POJO時如何包含類型信息,後者則利用元數據來確定重新創建對象的子類型。
為了確保序列化和反序列化都能正常工作,我們編寫一個測試用例來驗證整個過程。
首先,我們需要實例化一個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);接下來,BeanContainer 對象被序列化,並確認生成的字符串包含類型信息:
String jsonString = mapper.writeValueAsString(serializedContainer);
assertThat(jsonString, containsString("bean1"));
assertThat(jsonString, containsString("bean2"));以下是翻譯後的內容:
序列化的輸出如下所示:
{
"beans":
[
{
"@type": "bean1",
"id": 1,
"firstName": "Bean 1"
},
{
"@type": "bean2",
"id": 2,
"lastName": "Bean 2"
}
]
}這個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. 結論
本教程詳細介紹了多種不常見的 Jackson 註解。