1. Обзор
В этой статье рассматриваются некоторые дополнительные аннотации, которые не были рассмотрены в предыдущей статье «Руководство по аннотациям Джексона» — мы рассмотрим семь из них.
2. @JsonIdentityReference
@JsonIdentityReference
используется для настройки ссылок на объекты, которые будут сериализованы как идентификаторы объектов вместо полных POJO. Он работает в сотрудничестве с @JsonIdentityInfo
, чтобы принудительно использовать идентификаторы объектов в каждой сериализации, в отличие от всех случаев, кроме первого, когда @JsonIdentityReference
отсутствует. Эта пара аннотаций наиболее полезна при работе с циклическими зависимостями между объектами. Пожалуйста, обратитесь к разделу 4 статьи о Джексоне — Двунаправленные отношения для получения дополнительной информации.
Чтобы продемонстрировать использование @JsonIdentityReference
, мы определим два разных класса компонентов, без этой аннотации и с ней.
Компонент без @JsonIdentityReference
:
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class BeanWithoutIdentityReference {
private int id;
private String name;
// constructor, getters and setters
}
Для bean-компонента, использующего @JsonIdentityReference
, мы выбираем свойство 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
отсутствует, этот компонент сериализуется с полной информацией о его свойствах:
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, а не изменять определение класса. Например, может быть удобнее вставить метаданные версии
компонента в соответствующий документ JSON, чем предоставлять ему дополнительное свойство.
Предположим, у нас есть bean-компонент без @JsonAppend
следующим образом:
public class BeanWithoutAppend {
private int id;
private String name;
// constructor, getters and setters
}
Тест подтвердит, что при отсутствии аннотации @JsonAppend
вывод сериализации не содержит информации о дополнительном свойстве версии
, несмотря на то, что мы пытаемся добавить к объекту 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"
}
Теперь предположим, что у нас есть bean-компонент с аннотацией @JsonAppend
:
@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);
Вывод этой сериализации показывает, что свойство версии
было добавлено:
{
"id": 2,
"name": "Bean With Append Annotation",
"version": "1.0"
}
4. @JsonNaming
Аннотация @JsonNaming
используется для выбора стратегии именования свойств в сериализации, переопределяя значение по умолчанию. С помощью элемента value
мы можем указать любую стратегию, в том числе пользовательскую.
В дополнение к стандартному LOWER_CAMEL_CASE
(например , lowerCamelCase
), библиотека Джексона предоставляет нам четыре других встроенных стратегии именования свойств для удобства:
KEBAB_CASE
: элементы имени разделяются дефисами, напримерkebab-case
.LOWER_CASE
: все буквы в нижнем регистре без разделителей, напримерстрочные
.SNAKE_CASE
: все буквы в нижнем регистре с символами подчеркивания в качестве разделителей между элементами имени, например,змея_case
.UPPER_CAMEL_CASE
: Все элементы имени, включая первый, начинаются с заглавной буквы, за которой следуют строчные, и нет разделителей, например,UpperCamelCase
.
Этот пример иллюстрирует способ сериализации свойств с использованием имен 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
Библиотека Джексона умеет создавать схемы JSON для типов Java с помощью отдельного модуля под названием JSON Schema . Схема полезна, когда мы хотим указать ожидаемый результат при сериализации объектов Java или проверить документ JSON перед десериализацией.
Аннотация @JsonPropertyDescription
позволяет добавить удобочитаемое описание к созданной схеме JSON, предоставив поле описания
.
В этом разделе используется bean-компонент, объявленный ниже, для демонстрации возможностей @JsonPropertyDescription
:
public class PropertyDescriptionBean {
private int id;
@JsonPropertyDescription("This is a description of the name property")
private String name;
// getters and setters
}
Способ генерации схемы 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:foreach: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
}
Имена свойств бина отличаются от названий полей в строке JSON. Здесь на помощь приходит @JsonPOJOBuilder .
Аннотация @JsonPOJOBuilder
сопровождается двумя свойствами:
buildMethodName
: имя метода без аргументов, используемого для создания экземпляра ожидаемого компонента после привязки полей JSON к свойствам этого компонента. Имя по умолчанию —сборка
.withPrefix
: префикс имени для автоматического определения соответствия между JSON и свойствами bean-компонента. Префикс по умолчаниюс
.
В этом примере используется приведенный ниже класс 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,
и префикса конструкции
для сопоставления свойств.
Применение @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
используется для указания того, что аннотированное свойство должно быть сериализовано как идентификатор типа при включении информации о полиморфном типе, а не как обычное свойство. Эти полиморфные метаданные используются во время десериализации для воссоздания объектов тех же подтипов, какими они были до сериализации, а не объявленных супертипов.
Для получения дополнительной информации о том, как Джексон обращается с наследованием, см. Раздел 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.
Предположим, что мы хотим внедрить информацию о типе в строку 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, а второй определяет подтипы воссозданных объектов с использованием этих метаданных.
Чтобы убедиться, что и сериализация, и десериализация работают хорошо, давайте напишем тест для проверки всего прогресса.
Во-первых, нам нужно создать контейнер бина и классы бина, а затем заполнить этот контейнер экземплярами бина:
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. Заключение
В этом руководстве подробно описаны несколько менее распространенных аннотаций Джексона. Реализацию этих примеров и фрагментов кода можно найти в проекте GitHub .