Jackson — це популярна бібліотека для серіалізації/десеріалізації Java-об'єктів до різних текстових форматів. Основний функціонал для роботы з форматом JSON — клас ObjectMapper. А працювати з іншими форматами допоможуть його нащадки (XmlMapper, YAMLMapper). Завдяки успадкуванню робота зі всіма форматами відбуватиметься однаково, за допомогою єдиного інтерфейсу.

Качаємо jar-ники

Перед розбором прикладів потрібно завантажити jar-файли Jackson-у і підключити їх до проєкту в IntellijIDEA. Розглянемо, як шукати потрібні файли на прикладі jackson-databind:

  1. Перейди на сайт Maven Repository.

  2. У пошуковому рядку введи “jackson-databind”, отримаєш результат:

  3. Нас цікавить перший результат пошуку, переходимо за посиланням.

  4. Іноді може знадобитися конкретна версія бібліотеки, щоб забезпечити сумісність з іншими компонентами проєкту. Тобі підійде остання версія (на момент написання лекції це 2.13.2.2), переходь за посиланням.

  5. На сторінці, що відкрилася, тобі потрібне посилання “bundle”:

  6. Завантажуємо jar-файл за посиланням.

Аналогічно можна знайти та завантажити решту необхідних jar-ників:

Після завантаження потрібних файлів підключи їх до проєкту в IntellijIDEA:

  1. Відкрий налаштування проєкту (це можна зробити за допомогою комбінації Ctrl+Alt+Shift+S).

  2. Перейди до Libraries.

  3. Натисни +, потім Java, обери всі завантажені файли. Має вийти ось так:

  4. На цьому підготовку завершено, тож випробуємо ObjectMapper в дії.

Серіалізація в JSON

Спочатку серіалізуємо який-небудь об'єкт у JSON:


import com.fasterxml.jackson.databind.ObjectMapper;
 
class Book {
	public String title;
	public String author;
	public int pages;
}
 
public class Solution {
	public static void main(String[] args) throws Exception {
    	Book book = new Book();
    	book.title = "Джордж і незламний код";
    	book.author = "Гокінґ Л., Гокінґ С.";
    	book.pages = 320;
 
    	ObjectMapper mapper = new ObjectMapper();
    	String jsonBook = mapper.writeValueAsString(book);
    	System.out.println(jsonBook);
	}
}

Після запуску main отримаєш таке виведення:

{"title":"Джордж і незламний код","author":"Гокінґ Л., Гокінґ С.","pages":320}

У ObjectMapper-а є багато додаткових налаштувань. Скористаймося одним із них, щоб JSON-рядок був відформатованим таким чином, щоб його було зручно читати. Після створення об'єкту ObjectMapper виконаємо команду:

mapper.enable(SerializationFeature.INDENT_OUTPUT);

Інформація у виведенні залишається такою ж, але додалися відступи та перенесення рядків:

{
  "title" : "Джордж і незламний код",
  "author" : "Гокінґ Л., Гокінґ С.,
 "pages" : 320
}

Десеріалізація з JSON

Тепер виконаємо зворотну дію: десеріализуємо рядок в об'єкт. Щоб можна було оцінити роботу програми, до класу Book додамо метод toString:


@Override
public String toString() {
	return "Book{" +
        	"title='" + title + '\'' +
        	", author='" + author + '\'' +
        	", pages=" + pages +
        	'}';
}

І виконаємо такий main:


public static void main(String[] args) throws Exception {
	String jsonString = "{\"title\":\"Джордж і незламний код\",\"author\":\"Гокінґ Л., Гокінґ С.\",\"pages\":320}";
	Book book = new ObjectMapper().readValue(jsonString, Book.class);
	System.out.println(book);
}

Виведення:

Book{title='Джордж і незламний код', author='Гокінґ Л., Гокінґ С.', pages=320}

Метод readValue перевантажений, у нього є багато варіацій, що приймають файл, посилання, різноманітні потоки читання тощо. Для простоти в нашому прикладі використовується варіант, що приймає JSON у вигляді рядка.

Ми вже говорили, що ObjectMapper має багато налаштувань. Розглянемо деякі з них.

Ігноруємо невідомі властивості

Розглянемо ситуацію, коли в JSON-рядка є властивість, якої нема в класі Book:


public static void main(String[] args) throws Exception {
	String jsonString = """
        	{
          	"title" : "Джордж і незламний код",
          	"author" : "Гокінґ Л., Гокінґ С.",
          	"pages" : 320,
          	"unknown property" : 42
        	}""";
	ObjectMapper mapper = new ObjectMapper();
	Book book = mapper.readValue(jsonString, Book.class);
	System.out.println(book);
}

Якщо спробуємо виконати цей код, отримаємо UnrecognizedPropertyException. Таку поведінку встановлено за замовчуванням, але ми можемо її змінити:


ObjectMapper mapper =
new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

При створенні об'єкта ObjectMapper використовуємо метод configure, щоб установити необхідне налаштування в false. Метод configure змінює об'єкт, в якого його викликали, і повертає цей об'єкт, тому його можна викликати і по-іншому:


ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

Функціональністю такий запис є аналогічним до попереднього.

Якщо тепер запустити main, десеріалізація пройде успішно, а unknown property буде проігноровано.

Зручні анотації

Jackson надає нам низку анотацій для всебічної кастомізації серіалізації. Розглянемо декілька найкорисніших:

@JsonIgnore — ставиться над елементом, який потрібно ігнорувати при серіалізації/десеріалізації:


class Book {
	public String title;
	@JsonIgnore
	public String author;
	public int pages;
}

У результаті при серіалізації поле author не попадає до результуючого JSON. При десеріалізації поле author отримає значення за замовчуванням (null), навіть якщо в JSON-і було інше значення.

@JsonFormat — дозволяє задати формат серіалізованих даних. Додамо до класу Book ще одне поле типу Date:


class Book {
	public String title;
	public String author;
	public int pages;
	public Date createdDate = new Date();
}

Після серіалізації отримаємо такий JSON:

 {
  "title" : "Джордж і незламний код",
  "author" : "Гокінґ Л., Гокінґ С.",
  "pages" : 320,
  "createdDate" : 1649330880788
}

Як бачиш, дата серіалізувалася як число. Додамо анотацію та задамо формат:


class Book {
	public String title;
	public String author;
	public int pages;
	@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
	public Date createdDate = new Date();
}

Тепер результат серіалізації:

{
  "title" : "Джордж і незламний код",
  "author" : "Гокінґ Л., Гокінґ С.",
  "pages" : 320,
  "createdDate" : "2022-04-07"
}

@JsonProperty —дозволяє змінити ім'я властивості, до якої серіалізовано поле. Ще цією анотацією можна позначати методи, і їхнє значення, що повертається, буде перетворено до JSON-властивості при серіалізації:


class Book {
	@JsonProperty("name")
	public String title;
	public String author;
	public int pages;
 
	@JsonProperty("quotedTitle")
	public String getQuotedTitle() {
    	    return "«" + title + "»";
	}
}

Результат серіалізації:

{
  "author" : "Гокінґ Л., Гокінґ С.",
  "pages" : 320,
  "name" : "Джордж і незламний код",
  "quotedTitle" : "«Джордж і незламний код»"
}

@JsonInclude — за допомогою цієї анотації можна вказати, в якому випадку поле має серіалізуватися. Можна додавати як до окремих полів, так і до всього класу. Спочатку намагаємося серіалізувати об'єкт із неініціалізованими полями:


public class Solution {
	public static void main(String[] args) throws Exception {
    		Book book = new Book();

    		ObjectMapper mapper = new ObjectMapper();
    		mapper.enable(SerializationFeature.INDENT_OUTPUT);
    		String jsonBook = mapper.writeValueAsString(book);
    		System.out.println(jsonBook);
	}
}

Результат серіалізації:

{
  "title" : null,
  "author" : null,
  "pages" : 0
}

А якщо додати анотацію:


@JsonInclude(JsonInclude.Include.NON_NULL)
class Book {
	public String title;
	public String author;
	public int pages;
}

Отримаємо результат:

{
  "pages" : 0
}

Тепер поля, які мають значення null, не серіалізувалися.

@JsonPropertyOrder — дозволяє задати порядок серіалізації полів:


@JsonPropertyOrder({"author", "title", "pages"})
class Book {
	public String title;
	public String author;
	public int pages;
}

Результат серіалізації:

{
  "author" : "Гокінґ Л., Гокінґ С.",
  "title" : "Джордж і незламний код",
  "pages" : 320
}

Зараз можеш просто запам'ятати, як використовувати анотації, а під кінець цього модуля ми будемо знайомитися з ними детальніше і створювати власні анотації.

Серіалізація та десеріалізація в XML

Якщо потрібно серіалізувати в XML-формат, можемо також використовувати налаштування й анотації. Єдиною відмінністю буде реалізація об'єкта mapper:


public static void main(String[] args) throws Exception {
	Book book = new Book();
	book.title = "Джордж і незламний код";
	book.author = "Гокінґ Л., Гокінґ С.";
	book.pages = 320;
 
	ObjectMapper mapper = new XmlMapper();
	mapper.enable(SerializationFeature.INDENT_OUTPUT);
	String xmlBook = mapper.writeValueAsString(book);
	System.out.println(xmlBook);
}

Виведення:

 <Book>
  <title>Джордж і незламний код</title>
  <author>Гокінґ Л., Гокінґ С.</author>
  <pages>320</pages>
</Book>

Десеріалізація XML:


public static void main(String[] args) throws Exception {
   String xmlString = """
            <Book>
             <title>Джордж і незламний код</title>
             <author>Гокінґ Л., Гокінґ С.</author>
             <pages>320</pages>
           </Book>""";
   ObjectMapper mapper = new XmlMapper();
   Book book = mapper.readValue(xmlString, Book.class);
   System.out.println(book);
}

Серіалізація та десеріалізація в YAML

Аналогічно до XML вчиняємо і з YAML:


public static void main(String[] args) throws Exception {
	Book book = new Book();
	book.title = "Джордж і незламний код";
	book.author = "Гокінґ Л., Гокінґ С.";
	book.pages = 320;
 
	ObjectMapper mapper = new YAMLMapper();
	mapper.enable(SerializationFeature.INDENT_OUTPUT);
	String yamlBook = mapper.writeValueAsString(book);
	System.out.println(yamlBook);
}

Виведення:

---
title: "Джордж і незламний код"
author: "Гокінґ Л., Гокінґ С."
pages: 320

Десеріалізація YAML:


public static void main(String[] args) throws Exception {
   String yamlString = """
           ---
           title: "Джордж і незламний код"
           author: "Гокінґ Л., Гокінґ С."
           pages: 320""";
   ObjectMapper mapper = new YAMLMapper();
   Book book = mapper.readValue(yamlString, Book.class);
   System.out.println(book);
}