Перейти к содержимому

Как получить значение аннотаций jackson

  • автор:

Парсинг JSON с помощью Jackson

Большая часть веба на сегодняшний день обменивается данными в формате JSON. Веб-серверы, веб-приложения и мобильные приложения, даже устройства IoT общаются друг с другом, используя JSON. Простой и гибкий способ обработки JSON необходим любому программному обеспечению, чтобы выжить в современном мире.

Эта статья сопровождается примером рабочего кода на GitHub.

Что такое JSON?

JSON (от англ JavaScript Object Notation) — это текстовый формат для представления структурированных данных на основе синтаксиса объектов JavaScript. Благодаря своему гибкому и простому формату он стал чрезвычайно популярным. По сути, он следует модели карты «ключ-значение», допускающей вложенные объекты и массивы:

< "array": [ 1, 2, 3 ], "boolean": true, "color": "gold", "null": null, "number": 123, "object": < "a": "b", "c": "d" >, "string": "Hello World" >

Что такое Jackson?

Jackson в основном известен как библиотека, которая конвертирует строки JSON и простые объекты Java (англ POJO — Plain Old Java Object). Он также поддерживает многие другие форматы данных, такие как CSV, YML и XML.

Многие предпочитают Jackson благодаря его зрелости (он существует уже 13 лет) и отличной интеграции с популярными фреймворками, такими как Spring. Более того, это проект с открытым исходным кодом, который активно развивается и поддерживается широким сообществом.

Под капотом у Jackson есть три основных пакета: Streaming, Databind и Annotations. При этом Jackson предлагает нам три способа обработки преобразования JSON-POJO:

Потоковое API

Это самый быстрый подход из трех и с наименьшими накладными расходами. Он читает и записывает содержимое JSON в виде дискретных событий. API предоставляет JsonParser, который считывает JSON в POJO, и JsonGenerator, который записывает POJO в JSON.

Модель дерева

Модель дерева создает в памяти древовидное представление документа JSON. ObjectMapper отвечает за построение дерева из узлов JsonNode. Это наиболее гибкий подход, поскольку он позволяет перемещаться по дереву узлов, когда документ JSON не соответствует в достаточной мере POJO.

Привязка данных

Это позволяет нам выполнять преобразование между документами POJO и JSON с помощью средств доступа к свойствам или с помощью аннотаций. Он предлагает два типа привязки:

  • Простая привязка данных, которая преобразует JSON в Java Maps, Lists, Strings, Numbers, Booleans, null объекты и обратно.
  • Полная привязка данных, которая преобразует JSON в любой класс Java и из него.

ObjectMapper

ObjectMapper — наиболее часто используемая часть библиотеки Jackson, так как является самым простым способом преобразования между POJO и JSON. Она находится в com.fasterxml.jackson.databind .

Метод readValue() используется для преобразования (десериализации) JSON из строки, потока или файла в POJO.

С другой стороны, метод writeValue() используется для преобразования POJO в JSON (сериализация).

Способ, которым ObjectMapper определяет, какое поле JSON соответствует какому полю POJO, заключается в сопоставлении имен полей JSON с именами геттеров и сеттеров в POJO.

Это делается путем удаления частей «get» и «set» в именах геттеров и сеттеров и преобразования первого символа имени оставшегося метода в нижний регистр.

Например, предположим, у нас есть поле JSON с именем name : ObjectMapper сопоставит его с геттером getName() и сеттером setName() в POJO.

ObjectMapper является настраиваемым, и мы можем кастомизировать его в соответствии с нашими потребностями либо непосредственно через экземпляр ObjectMapper , либо с помощью аннотаций Jackson, как мы увидим позже.

Зависимости Maven

Прежде чем мы посмотрим на код, нам нужно добавить зависимость Jackson Maven jackson-databind, которая, в свою очередь, транзитивно добавляет jackson-annotations и jackson-core .

 com.fasterxml.jackson.core jackson-databind 2.13.3 

Мы также используем Lombok для обработки шаблонного кода для геттеров, сеттеров и конструкторов.

Базовая сериализация и десериализация JSON с Jackson

Давайте рассмотрим наиболее важные варианты использования Jackson с примерами кода.

Базовое преобразование POJO/JSON с использованием ObjectMapper

Давайте начнем с представления простого POJO под названием Employee:

@Getter @AllArgsConstructor @NoArgsConstructor public class Employee

Начнем с преобразования POJO в строку JSON:

public class JacksonTest < ObjectMapper objectMapper = new ObjectMapper(); @Test void pojoToJsonString() throws JsonProcessingException < Employee employee = new Employee("Mark", "James", 20); String json = objectMapper.writeValueAsString(employee); System.out.println(json); >>

В качестве вывода увидим следующее:

Теперь посмотрим, как преобразовать строку JSON в объект Employee с помощью ObjectMapper .

public class JacksonTest < . @Test void jsonStringToPojo() throws JsonProcessingException < String employeeJson = ""; Employee employee = objectMapper.readValue(employeeJson, Employee.class); assertThat(employee.getFirstName()).isEqualTo("Jalil"); > >

ObjectMapper также предлагает богатый API для чтения JSON из разных источников в разные форматы, давайте проверим самые важные из них.

Создание POJO из файла JSON

Это делается с помощью метода readValue() .

Файл JSON в тестовых ресурсах employee.json :

public class JacksonTest < . @Test void jsonFileToPojo() throws IOException < File file = new File("src/test/resources/employee.json"); Employee employee = objectMapper.readValue(file, Employee.class); assertThat(employee.getAge()).isEqualTo(44); assertThat(employee.getLastName()).isEqualTo("Simpson"); assertThat(employee.getFirstName()).isEqualTo("Homer"); >>
Создание POJO из массива байт в формате JSON
public class JacksonTest < . @Test void byteArrayToPojo() throws IOException < String employeeJson = ""; Employee employee = objectMapper.readValue(employeeJson.getBytes(), Employee.class); assertThat(employee.getFirstName()).isEqualTo("Jalil"); > >
Создание списка POJO из JSON

Иногда документ JSON представляет собой не объект, а список объектов. Давайте посмотрим, как можно его прочитать.

public class JacksonTest < . @Test void fileToListOfPojos() throws IOException < File file = new File("src/test/resources/employeeList.json"); ListemployeeList = objectMapper.readValue(file, new TypeReference<>()<>); assertThat(employeeList).hasSize(2); assertThat(employeeList.get(0).getAge()).isEqualTo(33); assertThat(employeeList.get(0).getLastName()).isEqualTo("Simpson"); assertThat(employeeList.get(0).getFirstName()).isEqualTo("Marge"); > >
Создание Map из JSON

Мы можем преобразовать JSON в Java Map , что очень удобно, если мы не знаем, чего ожидать от файла JSON, который мы пытаемся спарсить. ObjectMapper превратит имя каждой переменной в JSON в ключ для Map, а значение этой переменной — в значение по этому ключу.

public class JacksonTest < . @Test void fileToMap() throws IOException < File file = new File("src/test/resources/employee.json"); Mapemployee = objectMapper.readValue(file, new TypeReference<>()<>); assertThat(employee.keySet()).containsExactly("firstName", "lastName", "age"); assertThat(employee.get("firstName")).isEqualTo("Homer"); assertThat(employee.get("lastName")).isEqualTo("Simpson"); assertThat(employee.get("age")).isEqualTo(44); > >
Игнорирование неизвестных полей JSON

Иногда ожидаемый нами JSON может иметь дополнительные поля, не определенные в POJO. Поведение Jackson по умолчанию заключается в том, чтобы в таких случаях генерировать исключение UnrecognizedPropertyException . Однако же мы можем настроить Jackson так, чтобы он не расстраивался по поводу неизвестных полей и просто игнорировал их. Это делается путем установки FAIL_ON_UNKNOWN_PROPERTIES ObjectMapper в false.

public class JacksonTest < . @Test void fileToPojoWithUnknownProperties() throws IOException < File file = new File("src/test/resources/employeeWithUnknownProperties.json"); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); Employee employee = objectMapper.readValue(file, Employee.class); assertThat(employee.getFirstName()).isEqualTo("Homer"); assertThat(employee.getLastName()).isEqualTo("Simpson"); assertThat(employee.getAge()).isEqualTo(44); >>

Работа с датами in Jackson

Преобразование дат может быть непростым занятием, поскольку они могут быть представлены во многих форматах и уровнях спецификации (секунды, миллисекунды и т. д.).

Дата в JSON

Прежде чем говорить о преобразовании дат и Jackson, нам нужно поговорить о новом Date API в Java 8. Он был введен для устранения недостатков более старых java.util.Date и java.util.Calendar . В основном нас интересует использование класса LocalDate , который предлагает эффективный способ представления даты и времени.

Для этого нам нужно добавить в Jackson дополнительный модуль, чтобы он мог обрабатывать LocalDate .

 com.fasterxml.jackson.datatype jackson-datatype-jsr310 2.13.3 

Затем нам нужно сказать ObjectMapper найти и зарегистрировать новый модуль, который мы только что добавили.

public class JacksonTest < ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules(); . @Test void orderToJson() throws JsonProcessingException < Order order = new Order(1, LocalDate.of(1900,2,1)); String json = objectMapper.writeValueAsString(order); System.out.println(json); >>

В этом случае поведение Jackson по умолчанию состоит в том, чтобы показывать дату как [гггг-ММ-дд]. Таким образом, вывод будет

Однако мы можем указать Jackson, в каком формате нам нужна дата. Это можно сделать с помощью аннотации @JsonFormat .

public class Order < private int id; @JsonFormat(pattern = "dd/MM/yyyy") private LocalDate date; >
@Test void orderToJsonWithDate() throws JsonProcessingException
JSON в дату

Мы можем использовать ту же конфигурацию выше, чтобы преобразовать поле JSON в дату.

public class JacksonTest < . @Test void fileToOrder() throws IOException < File file = new File("src/test/resources/order.json"); Order order = objectMapper.readValue(file, Order.class); assertThat(order.getDate().getYear()).isEqualTo(2000); assertThat(order.getDate().getMonthValue()).isEqualTo(4); assertThat(order.getDate().getDayOfMonth()).isEqualTo(30); >>

Аннотации Jackson

Важную роль в настройке процесса преобразования JSON/POJO играют аннотации. Мы видели пример с преобразованием даты, где мы использовали аннотацию @JsonFormat . Аннотации влияют на то, как данные читаются, записываются или даже на то и другое. Давайте рассмотрим некоторые из этих аннотаций на основе их категорий.

Аннотации чтения

Они влияют на то, как Jackson преобразует JSON в POJO.

@JsonSetter

Это полезно, когда мы хотим сопоставить поле в строке JSON с полем в POJO, где их имена не совпадают.

@NoArgsConstructor @AllArgsConstructor @Getter public class Car
public class JacksonTest < . @Test void fileToCar() throws IOException < File file = new File("src/test/resources/car.json"); Car car = objectMapper.readValue(file, Car.class); assertThat(car.getBrand()).isEqualTo("BMW"); >>

@JsonAnySetter

Эта аннотация полезна в случаях, когда JSON содержит некоторые поля, не объявленные в POJO. Он используется с сеттером, который вызывается для каждого нераспознанного поля.

public class Car < @JsonSetter("carBrand") private String brand; private MapunrecognizedFields = new HashMap<>(); @JsonAnySetter public void allSetter(String fieldName, String fieldValue) < unrecognizedFields.put(fieldName, fieldValue); >>
public class JacksonTest < . @Test void fileToUnrecognizedCar() throws IOException < File file = new File("src/test/resources/carUnrecognized.json"); Car car = objectMapper.readValue(file, Car.class); assertThat(car.getUnrecognizedFields()).containsKey("productionYear"); >>
Аннотации записи

Они влияют на то, как Jackson преобразует POJO в JSON.

@JsonGetter

Это полезно, когда мы хотим сопоставить поле POJO с полем JSON, используя другое имя. Например, предположим, что у нас есть класс Cat с полем name , но мы хотим, чтобы его JSON-имя было catName .

@NoArgsConstructor @AllArgsConstructor public class Cat < private String name; @JsonGetter("catName") public String getName() < return name; >>
public class JacksonTest < . @Test void catToJson() throws JsonProcessingException < Cat cat = new Cat("Monica"); String json = objectMapper.writeValueAsString(cat); System.out.println(json); >>

@JsonAnyGetter

Эта аннотация позволяет нам использовать объект Map как источник свойств JSON. Скажем, у нас есть эта карта как поле в классе Cat .

@NoArgsConstructor @AllArgsConstructor public class Cat < private String name; @JsonAnyGetter Mapmap = Map.of( "name", "Jack", "surname", "wolfskin" ); . >
@Test void catToJsonWithMap() throws JsonProcessingException

Вывод будет следующим:

Аннотации чтения и записи

Эти аннотации влияют как на чтение, так и на запись JSON.

@JsonIgnore

Поле с аннотацией игнорируется как при записи, так и при чтении JSON.

@AllArgsConstructor @NoArgsConstructor @Getter public class Dog
public class JacksonTest < . @Test void dogToJson() throws JsonProcessingException < Dog dog = new Dog("Max", 3); String json = objectMapper.writeValueAsString(dog); System.out.println(json); >>

То же самое относится и к чтению в POJO.

Предположим, у нас есть файл dog.json :

public class JacksonTest < . @Test void fileToDog() throws IOException < File file = new File("src/test/resources/dog.json"); Dog dog = objectMapper.readValue(file, Dog.class); assertThat(dog.getName()).isEqualTo("bobby"); assertThat(dog.getAge()).isNull(); >>

У Jackson есть еще много полезных аннотаций, которые дают нам больше контроля над процессом сериализации/десериализации. Полный их список можно найти в репозитории Jackson на Github.

Резюме

  • Jackson — одна из самых мощных и популярных библиотек для обработки JSON в Java.
  • Jackson включает три основных модуля: Streaming API, Tree Model и Data Binding.
  • Jackson предоставляет ObjectMapper, который легко настраивается в соответствии с потребностями, с возможностью задавать его свойства и использовать аннотации.

Приглашаем всех желающих на открытый урок «Реляционные базы данных для начинающих Java-разработчиков». На уроке поговорим о месте реляционных баз данных в архитектуре информационных систем. Рассмотрим основные компоненты и возможности РСУБД на примере PostgreSQL. Сделаем обзор основных технологий по работе с реляционными БД в Java (JDBC, JPA/Hibernate, Spring Data и др.). Регистрируйтесь по ссылке.

  • Блог компании OTUS
  • Программирование
  • Java

Не самые очевидные советы по написанию DTO на Java

Сегодня приложения зачастую имеют распределенный характер. Для подключения к другим сервисам нужно писать больше кода — и при этом стараться сделать его простым.

Чтобы воспользоваться данными из внешней службы, мы обычно преобразуем полезную нагрузку JSON в объект передачи данных (Data Transfer Object, DTO). Код, обрабатывающий DTO, быстро усложняется, но с этим могут помочь несколько советов. Вполне возможно писать DTO, с которыми легче взаимодействовать и которые облегчают написание и чтение кода. Если объединить их вместе — можно упростить себе работу.

Сериализация DTO “по учебнику”

Начнем с типичного способа работы с JSON. Вот структура JSON. Этот JSON представляет пиццу “Реджина”.

< 
"name": "Regina",
"ingredients": ["Ham", "Mushrooms", "Mozzarella", "Tomato purée"]
>

Чтобы воспользоваться этими данными у себя в приложении, я создам простой DTO с именем PizzaDto .

import java.util.List;

public static class PizzaDto private String name;
private List ingredients;

public String getName() return name;
>

public void setName(String name) this.name = name;
>

public List getIngredients() return ingredients;
>

public void setIngredients(List ingredients) this.ingredients = ingredients;
>
>

PizzaDto — «старый добрый Java-объект», POJO: объект со свойствами, геттерами, сеттерами и всем остальным. Он отражает структуру JSON, поэтому преобразование между объектом и JSON занимает всего одну строку. Вот пример этого с библиотекой Jackson:

String json = """ 
<
"name": "Regina",
"ingredients": [ "Ham", "Mushrooms", "Mozzarella", "Tomato purée" ]
>
""";

// из JSON в объект
PizzaDto dto = new ObjectMapper().readValue(json, PizzaDto.class);

// из объекта JSON
json = new ObjectMapper().writeValueAsString(dto);

Преобразование простое и прямолинейное. В чем же тогда проблема?

В реальной жизни DTO бывают довольно сложными. Код для создания и инициализации DTO может включать вплоть до десятков строк. Иногда больше. Это проблема, потому что сложный код содержит больше ошибок и менее чувствителен к изменениям.

Моя первая попытка упростить создание DTO — воспользоваться неизменяемым DTO: таким, который нельзя модифицировать после создания.

Такой подход может показаться странным, если вы не знакомы с этой идеей, поэтому давайте сосредоточимся на ней поподробнее.

Создание неизменяемых DTO

Если говорить просто, то объект неизменяемый, если его состояние не может поменяться после сборки.

Давайте перепишем PizzaDto , чтобы сделать его неизменяемым.

import java.util.List;

public class PizzaDto
private final String name;
private final List ingredients;

public PizzaDto(String name, List ingredients) this.name = name;
if (ingredients != null) ingredients = List.copyOf(ingredients);
>
this.ingredients = ingredients;
>

public String getName() return name;
>

public List getIngredients() return ingredients;
>
>

У неизменяемого объекта нет сеттера. Все его свойства — окончательные и должны быть инициализированы при построении.

Как вы можете видеть, список ингредиентов не хранится как есть. Вместо этого для сохранения неизменяемой копии входных данных используется List.copyOf() . Это не позволяет клиентам изменять ингредиенты, хранящиеся в DTO.

dto 
.getIngredients()
.remove("Mushrooms"); // вызывает UnsupportedOperationException

Это важно, потому что пицца “Реджина” без грибов — уже определенно не пицца “Реджина”.

Если серьезнее, то Джошуа Блох, автор книги “Java: эффективное программирование”, дает такую рекомендацию для создания неизменяемых классов:

“Если в вашем классе есть какие-либо поля, которые ссылаются на изменяемые объекты, убедитесь, что клиенты класса не могут получать ссылки на эти объекты”. — Джошуа Блох

Если какое-либо свойство вашего DTO является изменяемым, вам необходимо сделать защитные копии. С их помощью вы предотвратите модификацию вашего DTO извне.

Примечание: начиная с Java 16, существует более краткий способ создания неизменяемых классов через записи.

Хорошо. Теперь у нас есть неизменяемый DTO. Но как это упрощает код?

Преимущества неизменяемости

Неизменяемость приносит много преимуществ, но вот мое любимое: неизменяемые переменные не имеют побочных эффектов.

Рассмотрим на примере. В этом фрагменте кода есть ошибка:

var pizza = make();
verify(pizza);
serve(pizza);

После выполнения этого кода пицца не содержит ожидаемого состояния. Какая строка вызвала проблему?

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

Первый ответ — с изменяемой пиццей. pizza создается с помощью make() , но ее можно изменить в рамках verify() и serve() . Таким образом, к ошибке может приводить любая строка из трех.

Теперь второй ответ — с неизменяемой пиццей. make() возвращает пиццу, но verify() и serve() не могут ее изменить. К проблеме может приводить только make() . Здесь гораздо меньше пространства для расследования. Ошибку легче найти.

С неизменяемыми переменными отладка становится проще. Но это еще не все.

Когда пицца не валидна, метод verify() , вероятно, создает исключение, чтобы прервать процесс. Изменим это. Нам нужно, чтобы метод verify() исправлял невалидные пиццы.

Поскольку pizza — неизменяемый объект, verify() не может просто исправить его. Придется создавать и возвращать измененную пиццу, а клиентский код необходимо адаптировать:

var pizza = make();
pizza = verify(pizza);
serve(pizza);

В этой новой версии очевидно, что метод verify() возвращает новую исправленную пиццу. Неизменяемость делает код более понятным. Его становится легче читать и легче развивать.

Возможно, вы не знаете, но мы и так каждый день пользуемся неизменяемыми объектами. java.lang.String , java.math.BigDecimal , java.io.File — все они неизменяемые.

Есть и другие преимущества В своей книге Джошуа Блох просто рекомендует “минимизировать изменчивость”.

“Неизменяемые классы проще проектировать, реализовывать и использовать, чем изменяемые классы. Они менее подвержены ошибкам и более безопасны”. — Джошуа Блох

Теперь возникает интересный вопрос: можем ли мы поступать так же с DTO?

Неизменяемые DTO… А это осмысленно?

Цель DTO — передача данных между процессами. Объект инициализируется, а затем его состояние не должно меняться. Либо он будет сериализован в JSON, либо будет использоваться клиентом. Это делает неизменность естественной. Неизменяемый DTO будет передавать данные между процессами с гарантией.

Тогда почему я сначала написал изменяемое PizzaDTO , а не неизменяемое? Дело в уверенности, что моей библиотеке JSON требуются геттеры и сеттеры для DTO.

Как оказалось, это не соответствует истине.

Неизменяемые DTO с Jackson

Jackson — самая распространенная JSON-библиотека для Java.

Когда у DTO есть геттеры и сеттеры, Jackson может сопоставить объект с JSON без какой-либо дополнительной настройки. Но с неизменяемыми объектами Jackson нуждается в небольшой помощи. Ему нужно знать, как собирать объект.

Конструктор объекта должен быть снабжен аннотацией @JsonCreator , а каждый аргумент — @JsonProperty . Добавим эти аннотации в конструктор DTO.

// новый импорт:
// import com.fasterxml.jackson.annotation.*;

@JsonCreator
public PizzaDto(
@JsonProperty("name") String name,
@JsonProperty("ingredients") List ingredients) this.name = name;
if (ingredients != null) ingredients = List.copyOf(ingredients);
>
this.ingredients = ingredients;
>

Вот и все. Теперь нас есть неизменяемый DTO, который Jackson может преобразовать в JSON и обратно в объект.

Неизменяемые DTO с Gson и Moshi

Есть две альтернативы Jackson: Gson и Moshi.

С помощью этих библиотек еще проще преобразовать JSON в неизменяемый DTO, потому что им не нужны никакие дополнительные аннотации.

Но почему Jackson вообще требует аннотаций, в отличие от Gson и Moshi?

Никакой магии. Дело в том, что, когда Gson и Moshi генерируют объект из JSON, они создают и инициализируют его путем отражения. Кроме того, они не задействуют конструкторы.

Я не большой поклонник такого подхода. Он вводит в заблуждение, потому что разработчик может вложить некоторую логику в конструктор и никогда не узнать, что он не вызывается. По сравнению с этим, Jackson представляется гораздо более безопасным.

Избегайте нулевых значений

У Jackson есть еще одно преимущество. Если поместить в конструктор некоторую логику, он будет вызываться всегда, независимо от того, создан ли DTO кодом приложения или сгенерирован из JSON.

Можно воспользоваться этим преимуществом для избегания значений null и улучшить конструктор для инициализации полей с ненулевыми значениями.

В приведенном ниже фрагменте кода поля инициализируются пустыми значениями, когда входные данные равны нулю.

// новый импорт :
// import static org.apache.commons.lang3.ObjectUtils.firstNonNull;

@JsonCreator
public PizzaDto(
@JsonProperty("name") String name,
@JsonProperty("ingredients") List ingredients) this.name = firstNonNull(name, ""); // replace null by empty String
this.ingredients = List.copyOf(
firstNonNull(ingredients, List.of()) // replace null by empty List
);
>

В большинстве случаев пустые значения и null не отличаются в поведении. Если заменить нулевые значения пустыми, клиенты смогут пользоваться свойствами DTO без предварительной проверки на null -значения. Кроме того, это снижает шанс появления NullPointerException.

Так вы напишете меньше кода и повысите надежность. Что может быть лучше?

И последнее по счету, но не по важности: создавайте DTO со строителями

Есть еще один совет, как упростить инициализацию DTO. В комплекте с каждым DTO я создаю Builder. Он предоставляет свободный API для облегчения инициализации DTO.

Вот пример создания PizzaDto через сборщик:

var pizza = new PizzaDto.Builder() 
.name("Regina")
.ingredients("Mozzarella cheese", "Basil leaves", "Olive oil", "Tomato purée")
.build();

С помощью сложных DTO разработчики делают код более выразительным. Этот шаблон настолько великолепен, что Джошуа Блох почти начинает с него свою книгу “Java: эффективное программирование”.

“Такой клиентский код легко писать и, что более важно, читать”. — Джошуа Блох

Как это работает? Объект builder просто хранит значения, пока мы не вызовем build() , который фактически создает нужный объект с сохраненными значениями.

Вот пример для PizzaDto :

public static final class Builder  
private String name;
private List ingredients;

public Builder name(String name) this.name = name;
return this;
>

public Builder ingredients(List ingredients) this.ingredients = ingredients;
return this;
>

/**
* перегружает чтобы тот принимал String varargs
*/
public Builder ingredients(String. ingredients) <
return ingredients(List.of(ingredients));
>

public PizzaDto build() return new PizzaDto(name, ingredients);
>
>

Некоторые пользуются Lombok для создания конструкторов во время компиляции. Это упрощает DTO.

Я предпочитаю генерировать код конструктора с помощью плагина Builder generator IntelliJ. Затем можно добавить перегрузки методов, как в предыдущем фрагменте кода. Конструктор таким образом становится более гибким, а клиентский код — более компактным.

Заключение

Вот основные советы, которые я держу в голове при написании DTO. Соединенные вместе, они действительно улучшат ваш код. Кодовая база становится легче для чтения, проще в обслуживании и, в конечном счете, так проще делиться ею с вашей командой.

  • Пишем асинхронный неблокирующий Rest API на Java
  • Состояния потоков в Java
  • Основы программирования UDP-сокетов на Java

Парсинг JSON с помощью Jackson

Большая часть веба на сегодняшний день обменивается данными в формате JSON. Веб-серверы, веб-приложения и мобильные приложения, даже устройства IoT общаются друг с другом, используя JSON. Простой и гибкий способ обработки JSON необходим любому программному обеспечению, чтобы выжить в современном мире.

Эта статья сопровождается примером рабочего кода на GitHub.

Что такое JSON?

JSON (от англ JavaScript Object Notation) — это текстовый формат для представления структурированных данных на основе синтаксиса объектов JavaScript. Благодаря своему гибкому и простому формату он стал чрезвычайно популярным. По сути, он следует модели карты «ключ-значение», допускающей вложенные объекты и массивы:

< "array": [ 1, 2, 3 ], "boolean": true, "color": "gold", "null": null, "number": 123, "object": < "a": "b", "c": "d" >, "string": "Hello World" >

Что такое Jackson?

Jackson в основном известен как библиотека, которая конвертирует строки JSON и простые объекты Java (англ POJO — Plain Old Java Object). Он также поддерживает многие другие форматы данных, такие как CSV, YML и XML.

Многие предпочитают Jackson благодаря его зрелости (он существует уже 13 лет) и отличной интеграции с популярными фреймворками, такими как Spring. Более того, это проект с открытым исходным кодом, который активно развивается и поддерживается широким сообществом.

Под капотом у Jackson есть три основных пакета: Streaming, Databind и Annotations. При этом Jackson предлагает нам три способа обработки преобразования JSON-POJO:

Потоковое API

Это самый быстрый подход из трех и с наименьшими накладными расходами. Он читает и записывает содержимое JSON в виде дискретных событий. API предоставляет JsonParser, который считывает JSON в POJO, и JsonGenerator, который записывает POJO в JSON.

Модель дерева

Модель дерева создает в памяти древовидное представление документа JSON. ObjectMapper отвечает за построение дерева из узлов JsonNode. Это наиболее гибкий подход, поскольку он позволяет перемещаться по дереву узлов, когда документ JSON не соответствует в достаточной мере POJO.

Привязка данных

Это позволяет нам выполнять преобразование между документами POJO и JSON с помощью средств доступа к свойствам или с помощью аннотаций. Он предлагает два типа привязки:

  • Простая привязка данных, которая преобразует JSON в Java Maps, Lists, Strings, Numbers, Booleans, null объекты и обратно.
  • Полная привязка данных, которая преобразует JSON в любой класс Java и из него.

ObjectMapper

ObjectMapper — наиболее часто используемая часть библиотеки Jackson, так как является самым простым способом преобразования между POJO и JSON. Она находится в com.fasterxml.jackson.databind .

Метод readValue() используется для преобразования (десериализации) JSON из строки, потока или файла в POJO.

С другой стороны, метод writeValue() используется для преобразования POJO в JSON (сериализация).

Способ, которым ObjectMapper определяет, какое поле JSON соответствует какому полю POJO, заключается в сопоставлении имен полей JSON с именами геттеров и сеттеров в POJO.

Это делается путем удаления частей «get» и «set» в именах геттеров и сеттеров и преобразования первого символа имени оставшегося метода в нижний регистр.

Например, предположим, у нас есть поле JSON с именем name : ObjectMapper сопоставит его с геттером getName() и сеттером setName() в POJO.

ObjectMapper является настраиваемым, и мы можем кастомизировать его в соответствии с нашими потребностями либо непосредственно через экземпляр ObjectMapper , либо с помощью аннотаций Jackson, как мы увидим позже.

Зависимости Maven

Прежде чем мы посмотрим на код, нам нужно добавить зависимость Jackson Maven jackson-databind, которая, в свою очередь, транзитивно добавляет jackson-annotations и jackson-core .

 com.fasterxml.jackson.core jackson-databind 2.13.3 

Мы также используем Lombok для обработки шаблонного кода для геттеров, сеттеров и конструкторов.

Базовая сериализация и десериализация JSON с Jackson

Давайте рассмотрим наиболее важные варианты использования Jackson с примерами кода.

Базовое преобразование POJO/JSON с использованием ObjectMapper

Давайте начнем с представления простого POJO под названием Employee:

@Getter @AllArgsConstructor @NoArgsConstructor public class Employee

Начнем с преобразования POJO в строку JSON:

public class JacksonTest < ObjectMapper objectMapper = new ObjectMapper(); @Test void pojoToJsonString() throws JsonProcessingException < Employee employee = new Employee("Mark", "James", 20); String json = objectMapper.writeValueAsString(employee); System.out.println(json); >>

В качестве вывода увидим следующее:

Теперь посмотрим, как преобразовать строку JSON в объект Employee с помощью ObjectMapper .

public class JacksonTest < . @Test void jsonStringToPojo() throws JsonProcessingException < String employeeJson = ""; Employee employee = objectMapper.readValue(employeeJson, Employee.class); assertThat(employee.getFirstName()).isEqualTo("Jalil"); > >

ObjectMapper также предлагает богатый API для чтения JSON из разных источников в разные форматы, давайте проверим самые важные из них.

Создание POJO из файла JSON

Это делается с помощью метода readValue() .

Файл JSON в тестовых ресурсах employee.json :

public class JacksonTest < . @Test void jsonFileToPojo() throws IOException < File file = new File("src/test/resources/employee.json"); Employee employee = objectMapper.readValue(file, Employee.class); assertThat(employee.getAge()).isEqualTo(44); assertThat(employee.getLastName()).isEqualTo("Simpson"); assertThat(employee.getFirstName()).isEqualTo("Homer"); >>
Создание POJO из массива байт в формате JSON
public class JacksonTest < . @Test void byteArrayToPojo() throws IOException < String employeeJson = ""; Employee employee = objectMapper.readValue(employeeJson.getBytes(), Employee.class); assertThat(employee.getFirstName()).isEqualTo("Jalil"); > >
Создание списка POJO из JSON

Иногда документ JSON представляет собой не объект, а список объектов. Давайте посмотрим, как можно его прочитать.

public class JacksonTest < . @Test void fileToListOfPojos() throws IOException < File file = new File("src/test/resources/employeeList.json"); ListemployeeList = objectMapper.readValue(file, new TypeReference<>()<>); assertThat(employeeList).hasSize(2); assertThat(employeeList.get(0).getAge()).isEqualTo(33); assertThat(employeeList.get(0).getLastName()).isEqualTo("Simpson"); assertThat(employeeList.get(0).getFirstName()).isEqualTo("Marge"); > >
Создание Map из JSON

Мы можем преобразовать JSON в Java Map , что очень удобно, если мы не знаем, чего ожидать от файла JSON, который мы пытаемся спарсить. ObjectMapper превратит имя каждой переменной в JSON в ключ для Map, а значение этой переменной — в значение по этому ключу.

public class JacksonTest < . @Test void fileToMap() throws IOException < File file = new File("src/test/resources/employee.json"); Mapemployee = objectMapper.readValue(file, new TypeReference<>()<>); assertThat(employee.keySet()).containsExactly("firstName", "lastName", "age"); assertThat(employee.get("firstName")).isEqualTo("Homer"); assertThat(employee.get("lastName")).isEqualTo("Simpson"); assertThat(employee.get("age")).isEqualTo(44); > >
Игнорирование неизвестных полей JSON

Иногда ожидаемый нами JSON может иметь дополнительные поля, не определенные в POJO. Поведение Jackson по умолчанию заключается в том, чтобы в таких случаях генерировать исключение UnrecognizedPropertyException . Однако же мы можем настроить Jackson так, чтобы он не расстраивался по поводу неизвестных полей и просто игнорировал их. Это делается путем установки FAIL_ON_UNKNOWN_PROPERTIES ObjectMapper в false.

public class JacksonTest < . @Test void fileToPojoWithUnknownProperties() throws IOException < File file = new File("src/test/resources/employeeWithUnknownProperties.json"); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); Employee employee = objectMapper.readValue(file, Employee.class); assertThat(employee.getFirstName()).isEqualTo("Homer"); assertThat(employee.getLastName()).isEqualTo("Simpson"); assertThat(employee.getAge()).isEqualTo(44); >>

Работа с датами in Jackson

Преобразование дат может быть непростым занятием, поскольку они могут быть представлены во многих форматах и уровнях спецификации (секунды, миллисекунды и т. д.).

Дата в JSON

Прежде чем говорить о преобразовании дат и Jackson, нам нужно поговорить о новом Date API в Java 8. Он был введен для устранения недостатков более старых java.util.Date и java.util.Calendar . В основном нас интересует использование класса LocalDate , который предлагает эффективный способ представления даты и времени.

Для этого нам нужно добавить в Jackson дополнительный модуль, чтобы он мог обрабатывать LocalDate .

 com.fasterxml.jackson.datatype jackson-datatype-jsr310 2.13.3 

Затем нам нужно сказать ObjectMapper найти и зарегистрировать новый модуль, который мы только что добавили.

public class JacksonTest < ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules(); . @Test void orderToJson() throws JsonProcessingException < Order order = new Order(1, LocalDate.of(1900,2,1)); String json = objectMapper.writeValueAsString(order); System.out.println(json); >>

В этом случае поведение Jackson по умолчанию состоит в том, чтобы показывать дату как [гггг-ММ-дд]. Таким образом, вывод будет

Однако мы можем указать Jackson, в каком формате нам нужна дата. Это можно сделать с помощью аннотации @JsonFormat .

public class Order < private int id; @JsonFormat(pattern = "dd/MM/yyyy") private LocalDate date; >
@Test void orderToJsonWithDate() throws JsonProcessingException
JSON в дату

Мы можем использовать ту же конфигурацию выше, чтобы преобразовать поле JSON в дату.

public class JacksonTest < . @Test void fileToOrder() throws IOException < File file = new File("src/test/resources/order.json"); Order order = objectMapper.readValue(file, Order.class); assertThat(order.getDate().getYear()).isEqualTo(2000); assertThat(order.getDate().getMonthValue()).isEqualTo(4); assertThat(order.getDate().getDayOfMonth()).isEqualTo(30); >>

Аннотации Jackson

Важную роль в настройке процесса преобразования JSON/POJO играют аннотации. Мы видели пример с преобразованием даты, где мы использовали аннотацию @JsonFormat . Аннотации влияют на то, как данные читаются, записываются или даже на то и другое. Давайте рассмотрим некоторые из этих аннотаций на основе их категорий.

Аннотации чтения

Они влияют на то, как Jackson преобразует JSON в POJO.

@JsonSetter

Это полезно, когда мы хотим сопоставить поле в строке JSON с полем в POJO, где их имена не совпадают.

@NoArgsConstructor @AllArgsConstructor @Getter public class Car
public class JacksonTest < . @Test void fileToCar() throws IOException < File file = new File("src/test/resources/car.json"); Car car = objectMapper.readValue(file, Car.class); assertThat(car.getBrand()).isEqualTo("BMW"); >>

@JsonAnySetter

Эта аннотация полезна в случаях, когда JSON содержит некоторые поля, не объявленные в POJO. Он используется с сеттером, который вызывается для каждого нераспознанного поля.

public class Car < @JsonSetter("carBrand") private String brand; private MapunrecognizedFields = new HashMap<>(); @JsonAnySetter public void allSetter(String fieldName, String fieldValue) < unrecognizedFields.put(fieldName, fieldValue); >>
public class JacksonTest < . @Test void fileToUnrecognizedCar() throws IOException < File file = new File("src/test/resources/carUnrecognized.json"); Car car = objectMapper.readValue(file, Car.class); assertThat(car.getUnrecognizedFields()).containsKey("productionYear"); >>
Аннотации записи

Они влияют на то, как Jackson преобразует POJO в JSON.

@JsonGetter

Это полезно, когда мы хотим сопоставить поле POJO с полем JSON, используя другое имя. Например, предположим, что у нас есть класс Cat с полем name , но мы хотим, чтобы его JSON-имя было catName .

@NoArgsConstructor @AllArgsConstructor public class Cat < private String name; @JsonGetter("catName") public String getName() < return name; >>
public class JacksonTest < . @Test void catToJson() throws JsonProcessingException < Cat cat = new Cat("Monica"); String json = objectMapper.writeValueAsString(cat); System.out.println(json); >>

@JsonAnyGetter

Эта аннотация позволяет нам использовать объект Map как источник свойств JSON. Скажем, у нас есть эта карта как поле в классе Cat .

@NoArgsConstructor @AllArgsConstructor public class Cat < private String name; @JsonAnyGetter Mapmap = Map.of( "name", "Jack", "surname", "wolfskin" ); . >
@Test void catToJsonWithMap() throws JsonProcessingException

Вывод будет следующим:

Аннотации чтения и записи

Эти аннотации влияют как на чтение, так и на запись JSON.

@JsonIgnore

Поле с аннотацией игнорируется как при записи, так и при чтении JSON.

@AllArgsConstructor @NoArgsConstructor @Getter public class Dog
public class JacksonTest < . @Test void dogToJson() throws JsonProcessingException < Dog dog = new Dog("Max", 3); String json = objectMapper.writeValueAsString(dog); System.out.println(json); >>

То же самое относится и к чтению в POJO.

Предположим, у нас есть файл dog.json :

public class JacksonTest < . @Test void fileToDog() throws IOException < File file = new File("src/test/resources/dog.json"); Dog dog = objectMapper.readValue(file, Dog.class); assertThat(dog.getName()).isEqualTo("bobby"); assertThat(dog.getAge()).isNull(); >>

У Jackson есть еще много полезных аннотаций, которые дают нам больше контроля над процессом сериализации/десериализации. Полный их список можно найти в репозитории Jackson на Github.

Резюме

  • Jackson — одна из самых мощных и популярных библиотек для обработки JSON в Java.
  • Jackson включает три основных модуля: Streaming API, Tree Model и Data Binding.
  • Jackson предоставляет ObjectMapper, который легко настраивается в соответствии с потребностями, с возможностью задавать его свойства и использовать аннотации.

Все примеры кода лежат в репозитории на GitHub.

Приглашаем всех желающих на открытый урок «Реляционные базы данных для начинающих Java-разработчиков». На уроке поговорим о месте реляционных баз данных в архитектуре информационных систем. Рассмотрим основные компоненты и возможности РСУБД на примере PostgreSQL. Сделаем обзор основных технологий по работе с реляционными БД в Java (JDBC, JPA/Hibernate, Spring Data и др.). Регистрируйтесь по ссылке.

Аннотации Jackson

В этой статье мы рассмотрим аннотации Jackson.
Мы изучим как использовать существующие аннотации Jackson и как создать свои.
А также как их отключить.

2. Аннотации Jackson для сериализации

Для начала рассмотрим аннотации применяемые для сериализации объектов.

2.1 @JsonAnyGetter

Аннотация @JsonAnyGetter позволяет сериализовать Map как обычные свойства класса, без вложенности.

Пример — класс ExtendableBean имеет фиксированное свойство name и поле с типом Map для задания произвольного количества атрибутов в виде пар key/value:

public class ExtendableBean < public String name; private Mapproperties; @JsonAnyGetter public Map getProperties() < return properties; >>

Сериализация без аннотации @JsonAnyGetter:

С аннотацией @JsonAnyGetter Jackson сериализует атрибуты key-value как обычные свойствами объекта, без вложенности:

2.2. @JsonGetter

Аннотация @JsonGetter (альтернатива аннотации @JsonProperty), которой можно пометить определённый метод как геттер.

В следующем примере помечаем разные методы класса MyBean аннотациями @JsonGetter :

public class MyBean < private int id; private String name; // свойство name существует @JsonGetter("name") public String getTheName() < return name; >// название метода без get. @JsonGetter public String testMethod1() < return "testMethod1"; >// название метода с get. @JsonGetter public String getTestMethod2() < return "getTestMethod2"; >// // свойство myProperty не существует @JsonGetter("myProperty") public String testMethod3() < return "testMethod3"; >>
 < "name": "BeanName", // getTheName -->name "testMethod1": "testMethod1", "testMethod2": "getTestMethod2", // getTestMethod2 --> testMethod2 "myProperty": "testMethod3" // testMethod3 --> myProperty >

2.3. @JsonPropertyOrder

Аннотация @JsonPropertyOrder используется для указания порядка свойств при сериализации объекта в JSON.

Давайте зададим свой порядок свойств класса MyBean:

@JsonPropertyOrder(< "name", "id" >) public class MyBean

2.4. @JsonRawValue

@JsonRawValue используется чтобы Jackson сериализовал свойство так как оно есть.

public class RawBean

2.5. @JsonValue

@JsonValue указывает на то что результат вызова метода должен использоваться как сериализация всего экземпляра класса.

Например для перечислений – мы помечаем метод getName аннотацией @JsonValue таким образом, что элементы перечисления сериализуются через значение свойства name:

public enum TypeEnumWithValue < TYPE1(1, "Type A"), TYPE2(2, "Type 2"); private Integer id; private String name; TypeEnumWithValue(Integer id, String name) < this.id = id; this.name = name; >@JsonValue public String getName() < return name; >>
// "TYPE1" - без @JsonValue // "Type A" - с @JsonValue @Test public void whenSerializingUsingJsonValue_thenCorrect() throws JsonParseException, IOException < String enumAsString = new ObjectMapper() .writeValueAsString(TypeEnumWithValue.TYPE1); // "Type A" assertThat(enumAsString, is("\"Type A\"")); >

2.6. @JsonRootName

Аннотация @JsonRootName используется, если включена вложенность (wrapping), для указания названия корневого элемента.

Вложенность означает, что при сериализации класса User вместо этого:

получим вот это:

Чтобы изменить название корневого элемента на «user» применим аннотацию @JsonRootName :

@JsonRootName(value = «user») public class UserWithRoot

По-умолчанию, названием корневого элемента будет название класса – UserWithRoot. Используя данную аннотацию, мы получим более удобное название — user:

@Test public void whenSerializingUsingJsonRootName_thenCorrect() throws JsonProcessingException

2.7. @JsonSerialize

@JsonSerialize применяется для указания класса, с помощью которого будет производиться сериализация сущности.

Рассмотрим пример – применим аннотацию @JsonSerialize для сериализации свойства eventDate с помощь класса CustomDateSerializer:

public class Event

Код простого кастомного сериализации Jackson:

public class CustomDateSerializer extends StdSerializer  < private static SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); public CustomDateSerializer() < this(null); >public CustomDateSerializer(Class t) < super(t); >@Override public void serialize( Date value, JsonGenerator gen, SerializerProvider arg2) throws IOException, JsonProcessingException < gen.writeString(formatter.format(value)); >>

@Test public void whenSerializingUsingJsonSerialize_thenCorrect() throws JsonProcessingException, ParseException

3. Jackson Deserialization Annotations

Далее рассмотрим аннотации Jackson для десериализации, т.е. для обратного преобразования JSON в Java-объект.

3.1. @JsonCreator

Аннотация @JsonCreator используется для настройки конструктора или фабрики, которые будут применяться при десериализации.

Это очень полезно когда нам нужно десериализовать некий JSON, который не в полной мере соответствует десериализуемому объекту.

Тут пригодится пример. Скажем, нам нужно десериализовать следующий JSON:

Однако, в нашем целевом классе нет поля theName – в нём есть только поле name. При этом мы не хотим менять наш класс. Мы можем указать Jackson, как нужно десериализовывать наш класс, отметив конструктор аннотацией @JsonCreator и используя @JsonProperty для аргументов:

public class BeanWithCreator < public int id; public String name; @JsonCreator public BeanWithCreator( @JsonProperty("id") int id, @JsonProperty("theName") String name) < this.id = id; this.name = name; >>
@Test public void whenDeserializingUsingJsonCreator_thenCorrect() throws IOException < String json = ""; BeanWithCreator bean = new ObjectMapper() .readerFor(BeanWithCreator.class) .readValue(json); assertEquals("My bean", bean.name); >

3.2. @JacksonInject

@JacksonInject используется чтобы пометить поле, в которое нужно внедрить значение, а не брать из данных JSON. При этом внедрение можно выполнить как по типу данных, так и по названию свойства.

В ниже приведённом примере мы используем @JacksonInject для внедрения своего значения в свойство id:

public class BeanWithInject

Обратите внимание, что в JSON нет значения для id, однако в десериализованном объекте оно уже есть:

@Test public void whenDeserializingUsingJsonInject_thenCorrect() throws IOException < String json = ""; InjectableValues inject = new InjectableValues.Std() .addValue(int.class, 1); BeanWithInject bean = new ObjectMapper().reader(inject) .forType(BeanWithInject.class) .readValue(json); assertEquals("My bean", bean.name); assertEquals(1, bean.id); >

3.3. @JsonAnySetter

@JsonAnySetter помечается метод, в который будут передаваться все нераспознанные поля и их значения. Помеченный метод должен иметь определенную сигнатуру. Таким образом можно складывать значение в Map.

Применим @JsonAnySetter для десериализации объектаExtendableBean:

public class ExtendableBean < public String name; private Mapproperties; @JsonAnySetter public void add(String key, String value) < properties.put(key, value); >>

JSON для десериализации:

Поле name попадёт в свойство name, а поля attr1 и attr2 попадут в метод add, т.к. в классе нет соответствующих свойств attr1, attr2.

@Test public void whenDeserializingUsingJsonAnySetter_thenCorrect() throws IOException < String json = ""; ExtendableBean bean = new ObjectMapper() .readerFor(ExtendableBean.class) .readValue(json); assertEquals("My bean", bean.name); assertEquals("val2", bean.getProperties().get("attr2")); >

3.4. @JsonSetter

@JsonSetter это альтернатива @JsonProperty – применяется для обозначения метода, который будет использоваться для установки значения свойству (сеттер).

Это очень полезно когда данные из JSON не совсем подходят под целевой класс или с данными нужно выполнить какие-то простые проверки или преобразования.

В следующем примере у нас есть метод setTheName() как сеттер свойства name в классе MyBean:

public class MyBean < public int id; private String name; @JsonSetter("name") public void setTheName(String name) < this.name = name; >>

Метод setTheName() будет использоваться для десериализации свойства name:

@Test public void whenDeserializingUsingJsonSetter_thenCorrect() throws IOException < String json = ""; MyBean bean = new ObjectMapper() .readerFor(MyBean.class) .readValue(json); assertEquals("My bean", bean.getTheName()); >

3.5. @JsonDeserialize

@JsonDeserialize используется для указания использовать указанный пользовательский десериализатор.

Пометим свойство eventDate аннотацией @JsonDeserialize для десериализации с помощью класса CustomDateDeserializer:

public class Event

Код нашего десериализатора:

public class CustomDateDeserializer extends StdDeserializer  < private static SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); public CustomDateDeserializer() < this(null); >public CustomDateDeserializer(Class vc) < super(vc); >@Override public Date deserialize( JsonParser jsonparser, DeserializationContext context) throws IOException < String date = jsonparser.getText(); try < return formatter.parse(date); >catch (ParseException e) < throw new RuntimeException(e); >> >
@Test public void whenDeserializingUsingJsonDeserialize_thenCorrect() throws IOException < String json = ""; SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); Event event = new ObjectMapper() .readerFor(Event.class) .readValue(json); assertEquals( "20-12-2014 02:30:00", df.format(event.eventDate)); >

4. Jackson Property Inclusion Annotations

4.1. @JsonIgnoreProperties

@JsonIgnoreProperties – one of the most common annotations in Jackson – is used to mark a property or a list of properties to be ignored at the class level.

Let’s go over a quick example ignoring the property id from serialization:

@JsonIgnoreProperties(< "id" >) public class BeanWithIgnore

And here’s the test making sure the ignore happens:

@Test public void whenSerializingUsingJsonIgnoreProperties_thenCorrect() throws JsonProcessingException

4.2. @JsonIgnore

The @JsonIgnore annotation is used to mark a property to be ignored at the field level.

Let’s use @JsonIgnore to ignore the property id from serialization:

public class BeanWithIgnore

And the test making sure that id was successfully ignored:

@Test public void whenSerializingUsingJsonIgnore_thenCorrect() throws JsonProcessingException

4.3. @JsonIgnoreType

@JsonIgnoreType is used to mark all properties of an annotated type to be ignored.

Let’s use the annotation to mark all properties of type Name to be ignored:

public class User < public int id; public Name name; @JsonIgnoreType public static class Name < public String firstName; public String lastName; >>

Here’s the simple test making sure the ignore works correctly:

@Test public void whenSerializingUsingJsonIgnoreType_thenCorrect() throws JsonProcessingException, ParseException

4.4. @JsonInclude

@JsonInclude is used to exclude properties with empty/null/default values.

Let’s look at an example – excluding nulls from serialization:

@JsonInclude(Include.NON_NULL) public class MyBean

Here’s the full test:

public void whenSerializingUsingJsonInclude_thenCorrect() throws JsonProcessingException

4.5. @JsonAutoDetect

@JsonAutoDetect is used to override the default semantics of which properties are visible and which are not.

Let’s take a look at how the annotation can be very helpful with a simple example – let’s enable serializing private properties:

@JsonAutoDetect(fieldVisibility = Visibility.ANY) public class PrivateBean
@Test public void whenSerializingUsingJsonAutoDetect_thenCorrect() throws JsonProcessingException

5. Jackson Polymorphic Type Handling Annotations

Next – let’s take a look at Jackson polymorphic type handling annotations:

  • @JsonTypeInfo is used to indicate details of what type information is included in serialization
  • @JsonSubTypes is used to indicate sub-types of annotated type
  • @JsonTypeName is used to define logical type name to use for annotated class

Let’s look at a more complex example and use all three – @JsonTypeInfo, @JsonSubTypes, and @JsonTypeName – to serialize/deserialize the entity Zoo:

public class Zoo < public Animal animal; @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = As.PROPERTY, property = "type") @JsonSubTypes(< @JsonSubTypes.Type(value = Dog.class, name = "dog"), @JsonSubTypes.Type(value = Cat.class, name = "cat") >) public static class Animal < public String name; >@JsonTypeName("dog") public static class Dog extends Animal < public double barkVolume; >@JsonTypeName("cat") public static class Cat extends Animal < boolean likesCream; public int lives; >>

When we do serialization:

@Test public void whenSerializingPolymorphic_thenCorrect() throws JsonProcessingException

Here’s what serializing the Zoo instance with the Dog will result in:

Now for de-serialization – let’s start with the following JSON input:

And let’s see how that gets unmarshalled to a Zoo instance:

6. Jackson General Annotations

Next – let’s discuss some of Jackson more general annotations.

6.1. @JsonProperty

@JsonProperty is used to indicate the property name in JSON.

Let’s go over the annotation with a simple example – and use @JsonProperty to serialize/deserialize the property name when we’re dealing with non-standard getters and setters:

public class MyBean < public int id; private String name; @JsonProperty("name") public void setTheName(String name) < this.name = name; >@JsonProperty("name") public String getTheName() < return name; >>

@Test public void whenUsingJsonProperty_thenCorrect() throws IOException

6.2. @JsonFormat

The @JsonFormat annotation can be used to specify a format when serializing Date/Time values.

In the following example – we use @JsonFormat to control the format of the property eventDate:

public class Event

And here’s the test:

@Test public void whenSerializingUsingJsonFormat_thenCorrect() throws JsonProcessingException, ParseException

6.3. @JsonUnwrapped

@JsonUnwrapped is used for defining values that should be unwrapped/flattened when serialized/deserialized.

Let’s see exactly how that works; we’ll use the annotation to unwrap the property name:

public class UnwrappedUser < public int id; @JsonUnwrapped public Name name; public static class Name < public String firstName; public String lastName; >>

Let’s now serialize an instance of this class:

@Test public void whenSerializingUsingJsonUnwrapped_thenCorrect() throws JsonProcessingException, ParseException

Here’s how the output looks like – the fields of the static nested class unwrapped along with the other field:

6.4. @JsonView

@JsonView is used to indicate the View in which the property will be included for serialization/deserialization.

An example will show exactly how that works – we’ll use @JsonView to serialize an instance of Item entity.

Let’s start with the views:

public class Views < public static class Public <>public static class Internal extends Public <> >

And now here’s the Item entity, using the views:

public class Item

Finally – the full test:

@Test public void whenSerializingUsingJsonView_thenCorrect() throws JsonProcessingException

6.5. @JsonManagedReference, @JsonBackReference

The @JsonManagedReference and @JsonBackReference annotations are used to handle parent/child relationships and work around loops.

In the following example – we use @JsonManagedReference and @JsonBackReference to serialize our ItemWithRef entity:

public class ItemWithRef

Our UserWithRef entity:

public class UserWithRef < public int id; public String name; @JsonBackReference public ListuserItems; >

@Test public void whenSerializingUsingJacksonReferenceAnnotation_thenCorrect() throws JsonProcessingException

6.6. @JsonIdentityInfo

@JsonIdentityInfo is used to indicate that Object Identity is to be used when serializing/deserializing values – for instance, to deal with infinite recursion type of problems.

In the following example – we have an ItemWithIdentity entity with a bidirectional relationship with the UserWithIdentity entity:

@JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = «id») public class ItemWithIdentity

And the UserWithIdentity entity:

@JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") public class UserWithIdentity < public int id; public String name; public ListuserItems; >

Now, let’s see how the infinite recursion problem is handled:

@Test public void whenSerializingUsingJsonIdentityInfo_thenCorrect() throws JsonProcessingException

Here’s the full output of the serialized item and user:

6.7. @JsonFilter

The @JsonFilter annotation specifies a filter to be used during serialization.

Let’s take a look at an example; first, we define the entity, and we point to the filter:

@JsonFilter(«myFilter») public class BeanWithFilter

Now, in the full test, we define the filter – which excludes all other properties except name from serialization:

@Test public void whenSerializingUsingJsonFilter_thenCorrect() throws JsonProcessingException

7. Custom Jackson Annotation

Next – let’s see how to create a custom Jackson annotation; we can make use of the @JacksonAnnotationsInside annotation – as in the following example:

@Retention(RetentionPolicy.RUNTIME) @JacksonAnnotationsInside @JsonInclude(Include.NON_NULL) @JsonPropertyOrder(< "name", "id", "dateCreated" >) public @interface CustomAnnotation

Now, if we use the new annotation on an entity:

@CustomAnnotation public class BeanWithCustomAnnotation

We can see how it does combine the existing annotations into a simpler, custom one that we can use as a shorthand:

@Test public void whenSerializingUsingCustomAnnotation_thenCorrect() throws JsonProcessingException

The output of the serialization process:

8. Jackson MixIn Annotations

Next – let’s see how to use Jackson MixIn annotations.

Let’s use the MixIn annotations to – for example – ignore properties of type User:

public class Item < public int id; public String itemName; public User owner; >@JsonIgnoreType public class MyMixInForIgnoreType

Let’s see this in action:

@Test public void whenSerializingUsingMixInAnnotation_thenCorrect() throws JsonProcessingException

9. Disable Jackson Annotation

Finally – let’s see how we can disable all Jackson annotations.We can do this by disabling the MapperFeature.USE_ANNOTATIONS as in the following example:

@JsonInclude(Include.NON_NULL) @JsonPropertyOrder(< "name", "id" >) public class MyBean

Now, after disabling annotations, these should have no effect and the defaults of the library should apply:

@Test public void whenDisablingAllAnnotations_thenAllDisabled() throws IOException

The result of serialization before disabling annotations:

The result of serialization after disabling annotations:

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

Мы рассмотрели аннотации Jackson, которые при правильном использовании дают вам гибкость при сериализации/десериализации объектов.

Видео от автора оригинальной статьи:

Код примеров может быть найден по ссылке GitHub project – это maven-проект, и поэтому вам будет его легко импортировать и запустить.

Статья является творческим переводом оригинальной статьи.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *