Java for Beginner
676 subscribers
559 photos
156 videos
12 files
856 links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Jackson

В мире Java-программирования одна из самых частых задач — это работа с JSON. Будь то обмен данными между сервером и клиентом, интеграция с внешними API или хранение конфигураций — JSON стал стандартом де-факто. Именно для этих целей и был создан Jackson.

Что такое Jackson

Jackson — это одна из самых популярных Java-библиотек для работы с JSON. Она позволяет сериализовать объекты Java в JSON и обратно — десериализовать JSON в Java-объекты. Причем делает это быстро, эффективно и с минимальными накладными расходами.

Быстрая история

2007 год — Джейсон Т. Смит (Jason T. Smith) начал разработку Jackson.
2009 год — Первый стабильный релиз (Jackson 1.x).
2012 год — Выпуск Jackson 2.0 с полностью переработанным API.
Сейчас — Jackson активно поддерживается, последняя версия (на 2024 год) — Jackson 2.16+.


Где используется Jackson

Сегодня Jackson используется практически везде, где в Java-приложениях требуется работа с JSON:
В REST API на базе Spring Boot и других фреймворков
В микросервисной архитектуре для обмена данными между сервисами
При интеграции с внешними веб-сервисами
Для сохранения и чтения конфигураций в формате JSON
В тестировании при генерации или проверке JSON-структур
Многие крупные компании и проекты строят на Jackson свою работу с данными. Более того, Spring Framework по умолчанию использует Jackson для сериализации и десериализации JSON, что дополнительно укрепило его позиции.


Почему Jackson так популярен

Есть несколько причин, почему Jackson стал стандартом:

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

Простота использования
Для базовых операций достаточно создать один экземпляр ObjectMapper и вызвать пару методов. В большинстве случаев не нужно писать дополнительный код.

Гибкость
Jackson поддерживает тонкую настройку через аннотации и внешнюю конфигурацию. Можно легко адаптировать сериализацию под любые требования.

Расширяемость
Jackson предлагает богатую экосистему модулей: поддержка новых типов данных (например, Java 8 Date/Time API), работа с CBOR, XML, YAML и другими форматами.

Интеграция с фреймворками
Такие популярные решения как Spring Boot, Dropwizard и многие другие "из коробки" работают с Jackson.

#Java #Training #Medium #Jackson
Jackson. Быстрая сериализация и десериализация объектов

Работа с JSON в Jackson строится вокруг главного класса — ObjectMapper. Это основной инструмент для конвертации Java-объектов в JSON и обратно.

ObjectMapper: главный рабочий инструмент

ObjectMapper — это универсальный класс, предоставляющий удобные методы для:
Сериализации (преобразования объекта в JSON)
Десериализации (преобразования JSON обратно в объект)


Создание экземпляра максимально простое:
ObjectMapper objectMapper = new ObjectMapper();


Сериализация объекта в JSON

Сериализация — это процесс превращения Java-объекта в строку JSON.

Рассмотрим пример:
import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {
public static void main(String[] args) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();

User user = new User("Alice", 25);

String jsonString = objectMapper.writeValueAsString(user);
System.out.println(jsonString);
}
}

class User {
private String name;
private int age;

public User() {
}

public User(String name, int age) {
this.name = name;
this.age = age;
}

// Геттеры и сеттеры обязательны для Jackson
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}


Результат работы программы:
{"name":"Alice","age":25}

writeValueAsString превращает объект в строку JSON. Можно также сохранить результат сразу в файл через writeValue(new File("user.json"), user).


Десериализация JSON в объект

Десериализация — это обратный процесс: превращение JSON-строки в объект Java.

Пример:
String jsonInput = "{\"name\":\"Bob\",\"age\":30}";

User user = objectMapper.readValue(jsonInput, User.class);

System.out.println(user.getName()); // Выведет: Bob
System.out.println(user.getAge()); // Выведет: 30

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


На что обратить внимание

🔹 Обязательны геттеры/сеттеры или public-поля (иначе Jackson не увидит поля).
🔹 Пустой конструктор нужен для десериализации.
🔹 Имена полей JSON и Java должны совпадать (или использовать аннотации, об этом в след. посте).
🔹 Обработка исключений — методы writeValueAsString() и readValue() могут выбросить JsonProcessingException.


#Java #Training #Medium #Jackson
Jackson

Ключевые аннотации для сериализации.


Jackson предоставляет богатый набор аннотаций для управления тем, как Java-объекты сериализуются в JSON. На практике чаще всего используются три основные аннотации: @JsonProperty, @JsonIgnore и @JsonInclude.

@JsonProperty

Аннотация @JsonProperty позволяет задать имя поля в итоговом JSON независимо от имени переменной в Java-классе. Это особенно полезно, если требуется соблюдать определенные соглашения об именовании в JSON (например, использовать snake_case).

Пример:
import com.fasterxml.jackson.annotation.JsonProperty;

public class User {
@JsonProperty("user_name")
private String name;

private int age;

public User() {}

public User(String name, int age) {
this.name = name;
this.age = age;
}

// Геттеры и сеттеры
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}


При сериализации объект будет выглядеть так:
{"user_name":"Alice","age":25}

Без @JsonProperty имя поля в JSON было бы "name", но аннотация позволила задать собственное имя.


@JsonIgnore

Аннотация @JsonIgnore используется для исключения полей из сериализации и десериализации. Если поле не должно попадать в JSON-вывод или не должно учитываться при чтении JSON, достаточно добавить эту аннотацию.

Пример:
import com.fasterxml.jackson.annotation.JsonIgnore;

public class User {
private String name;

@JsonIgnore
private String password;

public User() {}

public User(String name, String password) {
this.name = name;
this.password = password;
}

// Геттеры и сеттеры
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}


Результат сериализации:
{"name":"Alice"}

Поле password полностью исключено из JSON.


@JsonInclude

Аннотация @JsonInclude позволяет управлять включением полей в JSON в зависимости от их значения. Например, можно настроить сериализацию так, чтобы поля с null не попадали в итоговый JSON.

Пример:
import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
private String name;
private String email;

public User() {}

public User(String name, String email) {
this.name = name;
this.email = email;
}

// Геттеры и сеттеры
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}
}


Если email будет null, результат сериализации будет таким:
{"name":"Alice"}

Без @JsonInclude поле email также попало бы в JSON с null значением.


#Java #Training #Medium #Jackson #JsonProperty #JsonIgnore #JsonInclude
Jackson

Аннотации Jackson для десериализации и изменения структуры JSON

Иногда структура входящего JSON не совпадает напрямую с полями Java-объекта. Для корректной десериализации в таких случаях Jackson предлагает ряд аннотаций, среди которых особенно важны @JsonCreator, @JsonSetter и @JsonAlias.

@JsonCreator

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

Пример:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class User {
private final String name;
private final int age;

@JsonCreator
public User(@JsonProperty("name") String name,
@JsonProperty("age") int age) {
this.name = name;
this.age = age;
}

// Только геттеры
public String getName() {
return name;
}

public int getAge() {
return age;
}
}


При десериализации Jackson использует указанный конструктор, а не требует пустой конструктор и сеттеры:
String json = "{\"name\":\"Alice\",\"age\":30}";
User user = objectMapper.readValue(json, User.class);


@JsonSetter

Аннотация @JsonSetter указывается на методе и сообщает Jackson, что данный метод должен использоваться для установки значения при десериализации.
Это полезно, когда метод сеттера отличается по названию от поля или нужно выполнить какую-то дополнительную обработку значения.


Пример:
import com.fasterxml.jackson.annotation.JsonSetter;

public class Product {
private String name;

public Product() {}

public String getName() {
return name;
}

@JsonSetter("product_name")
public void setName(String name) {
this.name = name.toUpperCase();
}
}


JSON:
{
"product_name": "laptop"
}

При десериализации будет вызван метод setName, а имя продукта будет автоматически приведено к верхнему регистру: "LAPTOP".


@JsonAlias

Аннотация @JsonAlias позволяет указывать альтернативные имена полей, которые могут приходить в JSON. Это особенно полезно для работы с нестабильными или изменяющимися API.

Пример:
import com.fasterxml.jackson.annotation.JsonAlias;

public class Customer {
@JsonAlias({"user_name", "login"})
private String username;

public Customer() {}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}
}


Теперь следующий JSON будет корректно обработан:

{ "user_name": "admin" }


или

{ "login": "admin" }

И в обоих случаях значение попадет в поле username.


Пример нестандартного маппинга

Иногда API может менять структуру данных или использовать разные названия для одного и того же поля. Комбинируя @JsonCreator, @JsonProperty и @JsonAlias, можно добиться правильной обработки даже самых нестандартных случаев.

Например:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonAlias;

public class Employee {
private final String id;
private final String fullName;

@JsonCreator
public Employee(@JsonProperty("id") String id,
@JsonProperty("full_name") @JsonAlias({"name", "fullname"}) String fullName) {
this.id = id;
this.fullName = fullName;
}

public String getId() {
return id;
}

public String getFullName() {
return fullName;
}
}

Теперь Jackson корректно обработает JSON, где имя сотрудника приходит под разными ключами: full_name, name или fullname.


#Java #Training #Medium #Jackson #JsonCreator #JsonSetter #JsonAlias
Jackson

Работа со списками, картами и вложенными объектами

JSON — это не только простые объекты, но и сложные структуры: списки, словари, вложенные объекты. В этом посте разберем, как Jackson справляется с такими случаями, и как правильно сериализовать и десериализовать коллекции и составные структуры.

Списки объектов

Представим, что у нас есть список пользователей:
public class User {
private String name;
private int age;

public User() {}
public User(String name, int age) {
this.name = name;
this.age = age;
}

// Геттеры и сеттеры
}


Сериализация списка в JSON:
List<User> users = List.of(
new User("Alice", 30),
new User("Bob", 25)
);

String json = objectMapper.writeValueAsString(users);
System.out.println(json);


Результат:

[
{"name":"Alice","age":30},
{"name":"Bob","age":25}
]


Десериализация списка обратно в Java:
String jsonInput = "[{\"name\":\"Alice\",\"age\":30},{\"name\":\"Bob\",\"age\":25}]";

List<User> users = objectMapper.readValue(
jsonInput,
new TypeReference<List<User>>() {}
);
Здесь важно использовать TypeReference, чтобы сохранить информацию о типе во время десериализации.


Карты (мапы)

JSON-объект с произвольными ключами часто удобно мапить в Map<String, Object>.


Например:
{
"name": "Alice",
"age": 30,
"active": true
}


Можно прочитать в Map:
String json = "{\"name\":\"Alice\",\"age\":30,\"active\":true}";

Map<String, Object> map = objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {});


Сериализация карты обратно:
Map<String, Object> data = new HashMap<>();
data.put("name", "Alice");
data.put("age", 30);
data.put("active", true);

String jsonOutput = objectMapper.writeValueAsString(data);


Вложенные объекты

Вложенные структуры — это JSON, где одно поле содержит объект:
{
"id": 1,
"user": {
"name": "Alice",
"age": 30
}
}


Соответствующий Java-класс:
public class Wrapper {
private int id;
private User user;

public Wrapper() {}
public Wrapper(int id, User user) {
this.id = id;
this.user = user;
}

// Геттеры и сеттеры
}


Jackson автоматически корректно сериализует и десериализует такие вложенные объекты:
Wrapper wrapper = new Wrapper(1, new User("Alice", 30));
String json = objectMapper.writeValueAsString(wrapper);


Результат:
{
"id": 1,
"user": {
"name": "Alice",
"age": 30
}
}


И обратно:
String input = "{\"id\":1,\"user\":{\"name\":\"Alice\",\"age\":30}}";
Wrapper wrapper = objectMapper.readValue(input, Wrapper.class);


Комбинирование структур


Jackson легко справляется с комбинацией вложенных объектов, списков и мап:
{
"group": "admins",
"members": [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
],
"meta": {
"created": "2023-10-01",
"active": true
}
}


Нужно лишь правильно описать классы:
public class Group {
private String group;
private List<User> members;
private Map<String, Object> meta;

// Конструктор, геттеры, сеттеры
}


#Java #Training #Medium #Jackson
Jackson

Кастомные десериализаторы

Иногда структура входящего JSON или требования к обработке данных слишком специфичны, и стандартной десериализации Jackson оказывается недостаточно. В таких случаях можно реализовать собственный десериализатор, унаследовавшись от JsonDeserializer.

Когда нужен кастомный десериализатор

— Нужно преобразовать нестандартный формат данных
— Нужно добавить проверку, валидацию, нормализацию
— Нужно маппить одно поле в несколько
— Нужно логировать, фильтровать, трансформировать содержимое JSON


Шаг 1: Создание собственного десериализатора

Для этого создаем класс, расширяющий JsonDeserializer<T>:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;

import java.io.IOException;

public class NameDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String rawValue = p.getText();

if (rawValue == null || rawValue.trim().isEmpty()) {
throw new IOException("Имя не может быть пустым");
}

// Пример нормализации: первая буква заглавная
return rawValue.substring(0, 1).toUpperCase() + rawValue.substring(1).toLowerCase();
}
}


Шаг 2: Привязка десериализатора к полю

Теперь мы можем использовать наш кастомный десериализатор на нужном поле через аннотацию @JsonDeserialize.

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

public class User {
@JsonDeserialize(using = NameDeserializer.class)
private String name;

private int age;

public User() {}

// Геттеры и сеттеры
}


Шаг 3: Пример использования

String json = "{\"name\":\"  aLiCe  \", \"age\":30}";

User user = objectMapper.readValue(json, User.class);

System.out.println(user.getName()); // Выведет: Alice

Если в поле name будет пустая строка или только пробелы — Jackson выбросит исключение, как мы это настроили в десериализаторе.


Что ещё можно делать в кастомных десериализаторах

— Преобразовывать дату из нестандартного формата
— Разбирать строки с разделителями (например, CSV в List)
— Валидировать числовые диапазоны
— Преобразовывать строки в enum с логикой по умолчанию
— Обрабатывать null как спецзначения
— Инъектировать зависимости (через context.findInjectableValue())


#Java #Training #Medium #Jackson
Jackson

Глобальная настройка ObjectMapper: как управлять сериализацией и десериализацией

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

Инициализация ObjectMapper
import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper mapper = new ObjectMapper();
Можно использовать его напрямую или создать бин (например, в Spring).


1. Игнорирование неизвестных полей

По умолчанию Jackson выбросит исключение, если в JSON попадётся поле, которого нет в Java-классе.

Это можно отключить так:
import com.fasterxml.jackson.databind.DeserializationFeature;

mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);


Теперь лишние поля будут просто игнорироваться:
{
"name": "Alice",
"age": 30,
"extra_field": "ignored"
}


2. Красивая печать JSON (Pretty Print)

По умолчанию JSON сериализуется в одну строку. Чтобы включить форматирование с отступами:

import com.fasterxml.jackson.databind.SerializationFeature;

mapper.enable(SerializationFeature.INDENT_OUTPUT);


Пример вывода:

{
"name" : "Alice",
"age" : 30
}
Это особенно полезно для логирования и отладки.


3. Исключение null-полей

Если нужно убрать из JSON все поля с null, можно настроить так:
import com.fasterxml.jackson.annotation.JsonInclude;

mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);


Пример:
public class User {
public String name;
public String email; // может быть null
}


Результат без email, если он null:
{
"name": "Alice"
}


4. Ошибка при отсутствии обязательных полей

По умолчанию Jackson пропускает отсутствующие поля. Но можно требовать их наличия:
mapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, true);
mapper.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, true);


5. Написание чисел как строки

mapper.configure(SerializationFeature.WRITE_NUMBERS_AS_STRINGS, true);


Результат:
{
"age": "30"
}


6. Установка глобального формата дат
import java.text.SimpleDateFormat;

mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));


Когда это полезно
— В проектах с нестабильными API
— При интеграции с фронтендом, где важно форматирование
— При логировании JSON в читаемом виде
— Для обеспечения устойчивости к изменяющимся данным


#Java #Training #Medium #Jackson #ObjectMapper
Jackson

Использование модулей в Jackson: JavaTimeModule и работа с Java 8 датами

Начиная с Java 8, в язык была добавлена новая мощная модель работы со временем: LocalDate, LocalDateTime, Instant и другие типы из пакета java.time. Однако стандартная конфигурация Jackson не умеет корректно сериализовать и десериализовать эти типы "из коробки".

Проблема по умолчанию

Если попытаться сериализовать объект с полем LocalDateTime, Jackson выдаст ошибку или выведет нечитабельное представление (например, через timestamp).

Пример:
public class Event {
public String name;
public LocalDateTime dateTime;

public Event() {}
public Event(String name, LocalDateTime dateTime) {
this.name = name;
this.dateTime = dateTime;
}
}


Event event = new Event("Conference", LocalDateTime.now());
String json = objectMapper.writeValueAsString(event);
Без дополнительных модулей Jackson не поймёт, как сериализовать LocalDateTime.


Решение: JavaTimeModule

Чтобы Jackson начал понимать java.time.*, нужно подключить модуль JavaTimeModule.

Подключение (если используешь Maven):
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>


Регистрация модуля:
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());


Важно: если хочешь сериализовать даты в человекочитаемом формате, отключи запись timestamp:
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);


Пример работы
Event event = new Event("Conference", LocalDateTime.of(2025, 4, 29, 10, 30));
String json = mapper.writeValueAsString(event);
System.out.println(json);


Результат:
{
"name": "Conference",
"dateTime": "2025-04-29T10:30:00"
}


Аналогично работает и с другими типами:
LocalDate
LocalTime
ZonedDateTime
Instant


Десериализация обратно
String input = "{\"name\":\"Conference\",\"dateTime\":\"2025-04-29T10:30:00\"}";
Event event = mapper.readValue(input, Event.class);


Другие полезные модули Jackson

ParameterNamesModule — помогает при десериализации через конструкторы без @JsonProperty
Jdk8Module — поддержка Optional, OptionalInt и других типов из java.util
AfterburnerModule — ускоряет работу Jackson на больших объёмах данных


#Java #Training #Medium #Jackson
Jackson

Частичная сериализация с помощью @JsonView

Иногда возникает задача сериализовать один и тот же объект по-разному — в зависимости от ситуации, роли пользователя, уровня доступа или цели запроса. Например, показывать часть полей для публичного API и все — для администратора.

Jackson решает эту задачу с помощью механизма JSON Views.

Что такое JSON Views

@JsonView — это аннотация, позволяющая управлять, какие поля объекта попадут в JSON в зависимости от выбранного представления (view-класса).
Это как "фильтр по уровням доступа", встроенный прямо в сериализацию.

Шаг 1. Определяем уровни видимости (view)
public class Views {
public static class Public {}
public static class Internal extends Public {}
}
Можно создавать любые уровни — главное, чтобы они наследовались при необходимости.


Шаг 2. Аннотируем поля модели
import com.fasterxml.jackson.annotation.JsonView;

public class User {
@JsonView(Views.Public.class)
private String username;

@JsonView(Views.Internal.class)
private String email;

@JsonView(Views.Internal.class)
private String role;

public User() {}

public User(String username, String email, String role) {
this.username = username;
this.email = email;
this.role = role;
}

// Геттеры и сеттеры
}


Теперь:
username виден всем (включая Public)
email и role — только для Internal



Шаг 3. Сериализация с учетом view

ObjectMapper mapper = new ObjectMapper();

User user = new User("alice", "alice@example.com", "ADMIN");

// Публичный вид
String publicJson = mapper
.writerWithView(Views.Public.class)
.writeValueAsString(user);

System.out.println(publicJson);


Результат:

{
"username": "alice"
}


Если сериализовать с Internal view:

String internalJson = mapper
.writerWithView(Views.Internal.class)
.writeValueAsString(user);


Результат:
{
"username": "alice",
"email": "alice@example.com",
"role": "ADMIN"
}


Применение в REST (например, Spring)

В Spring можно использовать @JsonView прямо в контроллере:
@GetMapping("/user/public")
@JsonView(Views.Public.class)
public User getPublicUser() {
return userService.getCurrentUser();
}

@GetMapping("/user/internal")
@JsonView(Views.Internal.class)
public User getInternalUser() {
return userService.getCurrentUser();
}


Когда это нужно

Публичный/приватный API
Разные роли пользователей (гость, клиент, админ)
Безопасность: не передавать чувствительные поля
Удобство: не создавать множество DTO для каждой ситуации


#Java #Training #Medium #Jackson #JsonView
Jackson

Tree Model и Streaming API в Jackson: когда нужна гибкость и производительность

Jackson предлагает не только стандартную POJO-сериализацию, но и более гибкие модели работы с JSON. Среди них — Tree Model и Streaming API.
Tree Model даёт полную свободу при работе с JSON-структурой, а Streaming API — максимальную производительность при чтении/записи больших файлов.


Tree Model (JsonNode): работа с JSON как с деревом

Иногда структура JSON заранее неизвестна или слишком сложна, чтобы описывать её через Java-классы. В таких случаях удобно использовать дерево узлов — JsonNode.

Пример: чтение JSON как дерева
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

String json = "{ \"user\": { \"name\": \"Alice\", \"age\": 30 } }";

ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(json);

String name = rootNode.path("user").path("name").asText();
int age = rootNode.path("user").path("age").asInt();

System.out.println("Имя: " + name + ", Возраст: " + age);


Tree Model позволяет легко:
— обходить структуру вручную
— проверять наличие полей
— работать с вложенностью и массивами
— модифицировать JSON "на лету"


Когда использовать Tree Model

Когда структура JSON может меняться
Когда нужно читать только часть данных
При разработке универсальных парсеров
Когда сложно заранее описать модель через классы


Streaming API (JsonParser/JsonGenerator)

Для огромных JSON-файлов (например, логов, экспорта из БД) загрузка всего файла в память может быть невозможной. Здесь пригодится стриминговый API — он читает JSON по частям, как поток.

Пример: чтение JSON через JsonParser
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;

JsonFactory factory = new JsonFactory();
JsonParser parser = factory.createParser(new File("data.json"));

while (!parser.isClosed()) {
JsonToken token = parser.nextToken();

if (JsonToken.FIELD_NAME.equals(token) && "name".equals(parser.getCurrentName())) {
parser.nextToken();
System.out.println("Имя: " + parser.getValueAsString());
}
}
parser.close();


Когда использовать Streaming API


Работа с большими JSON-файлами, которые нельзя загрузить полностью
Требуется максимальная производительность
Необходимо читать JSON "по частям"
Парсинг в системах с ограниченной памятью (например, embedded)


Сравнение подходов

POJO-модель — просто, типобезопасно, но требует классов
Tree Model — гибко, удобно для анализа/модификации
Streaming API — быстро и экономно, но требует ручного контроля


#Java #Training #Medium #Jackson #Tree_Model #Streaming_API
Ключевые методы Optional

Optional предоставляет множество методов для работы с данными:

isPresent(): Возвращает true, если значение присутствует.
isEmpty() (добавлен в Java 11): Возвращает true, если значение отсутствует.
ifPresent(Consumer<? super T> consumer): Выполняет действие, если значение присутствует.
orElse(T other): Возвращает значение, если оно присутствует, или other, если отсутствует.
orElseGet(Supplier<? extends T> supplier): Возвращает значение или результат выполнения Supplier, если значение отсутствует.
orElseThrow(Supplier<? extends X> exceptionSupplier): Возвращает значение или выбрасывает исключение, если значение отсутствует.
map(Function<? super T, ? extends U> mapper): Преобразует значение, если оно присутствует, возвращая новый Optional.
flatMap(Function<? super T, Optional<U>> mapper): Преобразует значение, возвращая Optional, результат которого не оборачивается в дополнительный Optional.
filter(Predicate<? super T> predicate): Возвращает Optional, содержащий значение, если оно удовлетворяет предикату, или пустой Optional.
or(Supplier<? extends Optional<? extends T>> supplier) (добавлен в Java 9): Возвращает текущий Optional, если значение присутствует, или результат Supplier.


Пример использования методов
Optional<String> name = Optional.ofNullable(getName()); // Может вернуть null
String result = name
.map(String::toUpperCase)
.filter(s -> s.length() > 3)
.orElse("DEFAULT");
System.out.println(result); // Выведет преобразованное значение или "DEFAULT"

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



Преимущества Optional
Безопасность от NPE: Optional явно указывает на возможность отсутствия значения, заставляя разработчика обрабатывать этот случай.
Читаемость кода: Код с Optional более выразителен, так как сигнализирует о потенциальном отсутствии значения.
Избежание явного null: Уменьшает использование проверок if (value != null), делая код чище.
Интеграция с функциональным программированием: Методы map, flatMap и filter позволяют использовать Optional в цепочках обработки данных, интегрируясь с Stream API.



Практическое использование Optional

Интеграция со Stream API
Optional часто используется в связке со Stream API для обработки данных, которые могут отсутствовать. Например:
Optional<User> user = findUserById(123);
Stream<User> userStream = user.stream(); // Преобразует Optional в Stream
userStream.forEach(System.out::println);

Метод stream() (добавлен в Java 9) позволяет преобразовать Optional в Stream, содержащий либо одно значение, либо пустой.


Обработка вложенных объектов
Optional особенно полезен при работе с цепочками объектов, где каждое звено может быть null. Без Optional код выглядел бы так:
String city = null;
if (user != null && user.getAddress() != null && user.getAddress().getCity() != null) {
city = user.getAddress().getCity();
}


С использованием Optional код становится более компактным:
String city = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.orElse("Unknown");


Метод flatMap полезен, если методы возвращают Optional:
Optional<String> city = Optional.ofNullable(user)
.flatMap(u -> Optional.ofNullable(u.getAddress()))
.flatMap(a -> Optional.ofNullable(a.getCity()));


Использование в API
Optional часто применяется в публичных API для возврата значений, которые могут отсутствовать. Например, метод Map.getOrDefault может быть заменен на Optional:
Map<String, String> map = new HashMap<>();
Optional<String> value = Optional.ofNullable(map.get("key"));


#Java #Training #Medium #Optional
Антипаттерны и ограничения

Использование Optional для полей класса: Optional не предназначен для хранения состояния в полях класса, так как он не сериализуем (Optional не реализует Serializable). Вместо этого используйте null для полей или коллекции.
// Антипаттерн
public class User {
private Optional<String> name; // Не рекомендуется
}


Чрезмерное использование isPresent():
Проверки isPresent() с последующим вызовом get() сводят на нет преимущества Optional:

// Антипаттерн
if (optional.isPresent()) {
return optional.get();
}


Вместо этого используйте orElse, orElseGet или map
:
return optional.orElse(defaultValue);


Передача Optional в методы: Передача Optional в качестве аргумента метода может усложнить API. Лучше передавать значение или null:
// Антипаттерн
void process(Optional<String> value) { ... }

// Лучше
void process(String value) { ... }


Производительность: Создание объектов Optional в циклах или в высокопроизводительных системах может привести к накладным расходам. В таких случаях рассмотрите использование null или оптимизируйте код.
Сериализация: Optional не реализует Serializable, что делает его непригодным для использования в сериализуемых классах. Если сериализация необходима, преобразуйте Optional в null или значение перед сохранением.
Потокобезопасность: Optional не является потокобезопасным, так как он не предназначен для конкурентного доступа. Если значение внутри Optional изменяется в многопоточной среде, используйте синхронизацию или потокобезопасные альтернативы.


Производительность и оптимизация
Создание объектов: Каждый вызов Optional.of или ofNullable создает новый объект (кроме empty(), который использует синглтон). В критически важных участках кода минимизируйте создание Optional.
Кэширование: Если Optional возвращается часто используемым методом, рассмотрите кэширование результата, чтобы избежать повторного создания объектов.
Ленивые вычисления: Используйте orElseGet вместо orElse, если альтернативное значение требует вычислений:

// Менее эффективно
String result = optional.orElse(computeExpensiveDefault());

// Более эффективно
String result = optional.orElseGet(() -> computeExpensiveDefault());



Рекомендации

- Используйте Optional только там, где это имеет смысл: Применяйте Optional для возвращаемых значений методов, где отсутствие значения — это ожидаемый сценарий. Не используйте его для всех случаев, чтобы избежать ненужной сложности.
- Интеграция с функциональными API: Максимально используйте методы map, flatMap и filter для обработки данных в функциональном стиле. Это улучшает читаемость и поддерживаемость кода.
- Проверка на обратную совместимость: Если вы разрабатываете публичное API, учитывайте, что клиенты на Java 7 и ниже не смогут использовать Optional. В таких случаях предоставляйте альтернативные методы, возвращающие null.
- Тестирование: Убедитесь, что тесты покрывают оба случая — присутствие и отсутствие значения в Optional. Это гарантирует корректную обработку всех сценариев.
- Рефакторинг старого кода: При рефакторинге кода, использующего null, заменяйте проверки на Optional, но только если это улучшает читаемость и безопасность. Не заменяйте null на Optional механически.



Пример реального сценария

Предположим, у нас есть сервис, который возвращает информацию о пользователе:
public class UserService {
public Optional<User> findUserById(long id) {
// Имитация поиска в базе данных
return id == 123 ? Optional.of(new User("Alice")) : Optional.empty();
}

public Optional<String> getUserCity(long id) {
return findUserById(id)
.flatMap(user -> Optional.ofNullable(user.getAddress()))
.flatMap(address -> Optional.ofNullable(address.getCity()));
}
}

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


#Java #Training #Medium #Optional