1. Обзор
В этой статье рассматриваются некоторые дополнительные аннотации, которые не были рассмотрены в предыдущей статье, Руководство по аннотациям Джексона – мы рассмотрим семь из них.
2. @JsonIdentityReference
@JsonIdentityReference используется для настройки ссылок на объекты, которые будут сериализованы как идентификаторы объектов вместо полных POJOS. Он работает в сотрудничестве с @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 }
Для компонента , использующего @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 , вместо этого компонент сериализуется как простое удостоверение:
BeanWithIdentityReference bean = new BeanWithIdentityReference(1, "Bean With Identity Reference Annotation"); String jsonString = mapper.writeValueAsString(bean); assertEquals("1", jsonString);
3. @JsonAppend
Аннотация @JsonAppend используется для добавления виртуальных свойств к объекту в дополнение к обычным, когда этот объект сериализуется. Это необходимо, когда мы хотим добавить дополнительную информацию непосредственно в строку JSON, а не изменять определение класса. Например, может быть удобнее вставить метаданные version компонента в соответствующий документ JSON, чем предоставлять ему дополнительное свойство.
Предположим, что у нас есть боб без @JsonAppend следующим образом:
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 :
@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 ), библиотека Джексона предоставляет нам четыре других встроенных стратегии именования свойств для удобства:
- KEBAB_CASE : Элементы имени разделяются дефисами, например kebab-case .
- LOWER_CASE : Все буквы строчные без разделителей, например строчные .
- SNAKE_CASE : Все буквы строчные с подчеркиванием в качестве разделителей между элементами имени, например snake_case .
- UPPER_CAMEL_CASE : Все элементы имени, включая первый, начинаются с заглавной буквы, за которой следуют строчные буквы, и нет разделителей, например UpperCamelCase .
Этот пример проиллюстрирует способ сериализации свойств с использованием имен прецедентов змеи, где свойство с именем beanName сериализуется как bean_name.
Дано определение боба:
@JsonNaming(PropertyNamingStrategy.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"));
Переменная json String содержит следующие данные:
{ "id": 3, "bean_name": "Naming Bean" }
5. @Jobpropertydescriptor
Библиотека Джексона способна создавать схемы JSON для типов Java с помощью отдельного модуля под названием JSON Schema . Схема полезна, когда мы хотим указать ожидаемый результат при сериализации объектов Java или проверить документ JSON перед десериализацией.
Аннотация @Jobpropertydescriptor позволяет добавить удобочитаемое описание в созданную схему JSON, указав поле description .
В этом разделе используется компонент, объявленный ниже, чтобы продемонстрировать возможности @JsonPropertyDescription :
public class PropertyDescriptionBean { private int id; @JsonPropertyDescription("This is a description of the name property") private String name; // getters and setters }
Метод создания схемы JSON с добавлением поля description показан ниже:
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 для восстановления POJOS, когда соглашение об именовании отличается от соглашения по умолчанию.
Предположим, нам нужно десериализовать следующую строку 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 : Имя метода no-arg, используемого для создания экземпляра ожидаемого компонента после привязки полей JSON к свойствам этого компонента. Имя по умолчанию – build .
- с префиксом : Префикс имени для автоматического определения соответствия между свойствами 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 для использования метода сборки с именем create Bean и префикса construct для сопоставления свойств.
Применение @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
Аннотация @Jsontypeinfo используется для указания на то, что аннотированное свойство должно быть сериализовано как идентификатор типа при включении информации о полиморфном типе, а не как обычное свойство. Эти полиморфные метаданные используются во время десериализации для воссоздания объектов тех же подтипов, что и до сериализации, а не объявленных супертипов.
Дополнительные сведения об обработке Джексоном наследования см. в разделе 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 при работе со следующей иерархией классов.
Абстрактный суперкласс:
@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 }
Подкласс First Bean :
public class FirstBean extends AbstractBean { String firstName; public FirstBean(int id, String name) { super(id); setFirstName(name); } // no-arg constructor, getter and setter }
Подкласс Last Bean :
public class LastBean extends AbstractBean { String lastName; public LastBean(int id, String name) { super(id); setLastName(name); } // no-arg constructor, getter and setter }
Экземпляры этих классов используются для заполнения контейнера Bean объекта:
public class BeanContainer { private Listbeans; // 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); } }
Двумя наиболее заметными методами являются id Из значения и типа и type Из Id , причем первый указывает способ включения информации о типе при сериализации POJOS, а второй определяет подтипы воссозданных объектов с использованием этих метаданных.
Чтобы убедиться, что и сериализация, и десериализация работают хорошо, давайте напишем тест для проверки полного прогресса.
Сначала нам нужно создать экземпляр контейнера bean и классы bean, а затем заполнить этот контейнер экземплярами bean:
FirstBean bean1 = new FirstBean(1, "Bean 1"); LastBean bean2 = new LastBean(2, "Bean 2"); Listbeans = new ArrayList<>(); beans.add(bean1); beans.add(bean2); BeanContainer serializedContainer = new BeanContainer(); serializedContainer.setBeans(beans);
Затем объект Bean-контейнер сериализуется, и мы подтверждаем, что полученная строка содержит информацию о типе:
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); ListbeanList = deserializedContainer.getBeans(); assertThat(beanList.get(0), instanceOf(FirstBean.class)); assertThat(beanList.get(1), instanceOf(LastBean.class));
9. Заключение
В этом учебнике подробно описано несколько менее распространенных аннотаций Джексона. Реализацию этих примеров и фрагментов кода можно найти в проекте GitHub .