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 List 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);
}
}Двумя наиболее заметными методами являются 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 .