Още анотации на Джаксън

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 }

За боб, използващ @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 низ, вместо да променяме дефиницията на класа. Например може да е по-удобно да вмъкнете метаданните на версията на боб в съответния JSON документ, отколкото да му предоставите допълнително свойство.

Да приемем, че имаме боб без @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" }

Да кажем, че имаме боб, коментиран с @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 анотацията се използва, за да изберете стратегии за именуване за имоти в сериализация, първостепенни по подразбиране. Използвайки елемента стойност , можем да посочим всяка стратегия, включително персонализирана.

В допълнение към подразбирането, което е LOWER_CAMEL_CASE (напр. LowerCamelCase ), библиотеката на Джаксън ни предоставя четири други вградени стратегии за именуване на свойства за удобство:

  • 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"));

В jsonString променливата съдържа следните данни:

{ "id": 3, "bean_name": "Naming Bean" }

5. @JsonPropertyDescription

Библиотеката на Jackson е в състояние да създава JSON схеми за типове Java с помощта на отделен модул, наречен JSON Schema. Схемата е полезна, когато искаме да посочим очаквания изход при сериализиране на Java обекти или да проверим JSON документ преди десериализация.

В @JsonPropertyDescription анотацията позволява на човек чете описание да бъде добавен към създал схемата JSON чрез предоставяне на описание терена.

Този раздел използва боб, деклариран по-долу, за да демонстрира възможностите на @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:baeldung:jackson:annotation:extra:PropertyDescriptionBean", "properties": { "name": { "type": "string", "description": "This is a description of the name property" }, "id": { "type": "integer" } } }

6. @JsonPOJOBuilder

В @JsonPOJOBuilder анотацията се използва за конфигуриране на строител клас, за да персонализирате deserialization на документ за 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 .
  • withPrefix : Именният префикс за автоматично откриване на съвпадение между свойствата на JSON и боб. Префиксът по подразбиране е с .

Този пример използва класа 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 към боб е описано и тествано, както следва:

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 от наследството в Джаксън.

Да приемем, че имаме дефиниция на клас на боб, както следва:

public class TypeIdBean { private int id; @JsonTypeId private String name; // constructor, getters and setters }

The following test validates that @JsonTypeId works as it is meant to:

mapper.enableDefaultTyping(DefaultTyping.NON_FINAL); TypeIdBean bean = new TypeIdBean(6, "Type Id Bean"); String jsonString = mapper.writeValueAsString(bean); assertThat(jsonString, containsString("Type Id Bean"));

The serialization process' output:

[ "Type Id Bean", { "id": 6 } ]

8. @JsonTypeIdResolver

The @JsonTypeIdResolver annotation is used to signify a custom type identity handler in serialization and deserialization. That handler is responsible for conversion between Java types and type id included in a JSON document.

Suppose that we want to embed type information in a JSON string when dealing with the following class hierarchy.

The AbstractBean superclass:

@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 }

The FirstBean subclass:

public class FirstBean extends AbstractBean { String firstName; public FirstBean(int id, String name) { super(id); setFirstName(name); } // no-arg constructor, getter and setter }

The LastBean subclass:

public class LastBean extends AbstractBean { String lastName; public LastBean(int id, String name) { super(id); setLastName(name); } // no-arg constructor, getter and setter }

Instances of those classes are used to populate a BeanContainer object:

public class BeanContainer { private List beans; // getter and setter }

We can see that the AbstractBean class is annotated with @JsonTypeIdResolver, indicating that it uses a custom TypeIdResolver to decide how to include subtype information in serialization and how to make use of that metadata the other way round.

Here is the resolver class to handle inclusion of type information:

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); } }

The two most notable methods are idFromValueAndType and typeFromId, with the former telling the way to include type information when serializing POJOs and the latter determining the subtypes of re-created objects using that metadata.

In order to make sure that both serialization and deserialization work well, let's write a test to validate the complete progress.

Първо, трябва да създадем екземпляр на контейнер за боб и класове боб, след което да попълним този контейнер с екземпляри на боб:

FirstBean bean1 = new FirstBean(1, "Bean 1"); LastBean bean2 = new LastBean(2, "Bean 2"); List 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 beanList = deserializedContainer.getBeans(); assertThat(beanList.get(0), instanceOf(FirstBean.class)); assertThat(beanList.get(1), instanceOf(LastBean.class));

9. Заключение

Този урок обяснява подробно няколко по-рядко срещани анотации на Джаксън. Изпълнението на тези примери и кодови фрагменти може да бъде намерено в проект на GitHub.