更多 Jackson 注解
最后更新时间:2021年10月28日
1. 概述
本文涵盖了一些先前文章Jackson 注解指南中未涉及的额外注解 – 我们将介绍其中的七个。
2. @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"
}
当使用@JsonIdentityReference 时,该 bean 将序列化为简单的标识
BeanWithIdentityReference bean
= new BeanWithIdentityReference(1, "Bean With Identity Reference Annotation");
String jsonString = mapper.writeValueAsString(bean);
assertEquals("1", jsonString);
3. @JsonAppend
@JsonAppend 注解用于在对象序列化时,除了常规属性外,添加虚拟属性。当我们需要将补充信息直接插入到 JSON 字符串中,而不是更改类定义时,这是必要的。例如,将 bean 的version 元数据插入到相应的 JSON 文档中,可能比使用附加属性来提供它更方便。
假设我们有一个没有@JsonAppend 的 bean,如下所示
public class BeanWithoutAppend {
private int id;
private String name;
// constructor, getters and setters
}
测试将确认,在没有@JsonAppend 注解的情况下,序列化输出不包含关于补充version 属性的信息,尽管我们尝试将其添加到ObjectWriter 对象中
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. @JsonNaming
@JsonNaming 注解用于选择序列化中属性的命名策略,覆盖默认设置。使用value 元素,我们可以指定任何策略,包括自定义策略。
除了默认值LOWER_CAMEL_CASE(例如 lowerCamelCase)之外,Jackson 库还为我们提供了四个内置的属性命名策略以方便使用
- KEBAB_CASE:名称元素由连字符分隔,例如 kebab-case。
- LOWER_CASE:所有字母均为小写,没有分隔符,例如 lowercase。
- SNAKE_CASE:所有字母均为小写,名称元素之间用下划线分隔,例如 snake_case。
- UPPER_CAMEL_CASE:所有名称元素(包括第一个),都以大写字母开头,后跟小写字母,并且没有分隔符,例如 UpperCamelCase。
本示例将说明使用 snake case 名称序列化属性的方式,其中名为beanName 的属性序列化为bean_name.
给定一个 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. @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 注解用于配置 builder 类,以自定义 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:用于实例化绑定 JSON 字段到该 bean 属性的预期 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 前缀来匹配属性。
对 bean 应用 @JsonPOJOBuilder 的描述和测试如下:
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
@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 来决定如何在序列化中包含子类型信息以及如何反向使用该元数据。
这里是用于处理类型信息包含的解析器类
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 注解。
支持本文的代码可在 GitHub 上获取。 一旦你以 Baeldung Pro 会员 身份登录,就开始学习并在项目上进行编码。















