Serialization (kotlinx) для Kotlin: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое kotlinx.serialization и зачем она нужна?

kotlinx.serialization — это официальная библиотека от JetBrains для сериализации и десериализации данных в Kotlin. Она предоставляет мощный, типобезопасный и мультиплатформенный способ преобразования объектов Kotlin в различные форматы (JSON, Protobuf, CBOR, YAML и другие) и обратно.

Основные причины использовать эту библиотеку:

  • Мультиплатформенность: Работает на JVM, JS, Native (iOS, Android, Desktop).
  • Типобезопасность: Сериализация проверяется на этапе компиляции, что исключает ошибки во время выполнения.
  • Производительность: Использует рефлексию только при необходимости (через плагин компилятора).
  • Гибкость: Поддерживает кастомные сериализаторы, полиморфизм, опциональные поля и многое другое.
  • Интеграция с Kotlin: Полностью использует возможности языка (data class, sealed class, null safety).

Установка и настройка

Для использования kotlinx.serialization необходимо добавить плагин компилятора и зависимость в ваш проект.

1. Добавление плагина (build.gradle.kts):

plugins {
    kotlin("jvm") version "1.9.0" // или ваша версия Kotlin
    kotlin("plugin.serialization") version "1.9.0"
}

2. Добавление зависимости:

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
}

3. Для Maven (pom.xml):

<plugin>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-maven-plugin</artifactId>
    <version>1.9.0</version>
    <configuration>
        <pluginOptions>
            <option>plugin:org.jetbrains.kotlin.plugin.serialization</option>
        </pluginOptions>
    </configuration>
</plugin>

<dependency>
    <groupId>org.jetbrains.kotlinx</groupId>
    <artifactId>kotlinx-serialization-json</artifactId>
    <version>1.6.2</version>
</dependency>

Быстрый старт: первый пример

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString

// 1. Определяем сериализуемый класс
@Serializable
data class User(val name: String, val age: Int, val email: String? = null)

fun main() {
    // 2. Создаем объект
    val user = User("Alice", 30, "alice@example.com")

    // 3. Сериализуем в JSON
    val jsonString = Json.encodeToString(user)
    println("Сериализованный JSON: $jsonString")

    // 4. Десериализуем обратно
    val decodedUser = Json.decodeFromString<User>(jsonString)
    println("Десериализованный объект: $decodedUser")
}

Вывод:

Сериализованный JSON: {"name":"Alice","age":30,"email":"alice@example.com"}
Десериализованный объект: User(name=Alice, age=30, email=alice@example.com)

Основные методы и функции (8+)

1. Json.encodeToString()

Что делает: Сериализует объект Kotlin в строку JSON.

Параметры:

  • value (T) — объект для сериализации.
  • serializer (Serializer<T>?) — опциональный кастомный сериализатор.
val user = User("Bob", 25)
val json = Json.encodeToString(user)
println(json) // {"name":"Bob","age":25}

2. Json.decodeFromString()

Что делает: Десериализует строку JSON в объект Kotlin.

Параметры:

  • string (String) — JSON строка.
  • deserializer (DeserializationStrategy<T>) — опциональный десериализатор.
val json = """{"name":"Charlie","age":35}"""
val user = Json.decodeFromString<User>(json)
println(user) // User(name=Charlie, age=35, email=null)

3. Json.encodeToByteArray()

Что делает: Сериализует объект в массив байтов (полезно для сетевых протоколов).

Параметры: Аналогично encodeToString().

val user = User("Diana", 28)
val bytes = Json.encodeToByteArray(user)
println(bytes.contentToString()) // [123, 34, 110, ...]

4. Json.decodeFromByteArray()

Что делает: Десериализует массив байтов в объект.

Параметры:

  • bytes (ByteArray) — массив байтов.
val jsonBytes = """{"name":"Eve","age":22}""".toByteArray()
val user = Json.decodeFromByteArray<User>(jsonBytes)
println(user) // User(name=Eve, age=22, email=null)

5. Json.encodeToStream()

Что делает: Сериализует объект и записывает в OutputStream.

Параметры:

  • stream (OutputStream) — поток вывода.
  • value (T) — объект.
val user = User("Frank", 40)
val outputStream = ByteArrayOutputStream()
Json.encodeToStream(user, outputStream)
println(outputStream.toString()) // {"name":"Frank","age":40}

6. Json.decodeFromStream()

Что делает: Десериализует данные из InputStream.

Параметры:

  • stream (InputStream) — поток ввода.
val jsonString = """{"name":"Grace","age":33}"""
val inputStream = jsonString.byteInputStream()
val user = Json.decodeFromStream<User>(inputStream)
println(user) // User(name=Grace, age=33, email=null)

7. Json.parseToJsonElement()

Что делает: Парсит JSON строку в JsonElement (для работы с динамической структурой).

Параметры:

  • string (String) — JSON строка.
val json = """{"name":"Henry","age":45,"hobbies":["reading","swimming"]}"""
val element = Json.parseToJsonElement(json)
val obj = element.jsonObject
println(obj["name"]) // "Henry"
println(obj["hobbies"]?.jsonArray?.get(0)) // "reading"

8. JsonBuilder (конфигурация)

Что делает: Позволяет настроить поведение сериализатора (игнорирование неизвестных ключей, форматирование и т.д.).

val json = Json {
    prettyPrint = true
    ignoreUnknownKeys = true
    isLenient = true
}

data class Config(val host: String, val port: Int)

val config = json.decodeFromString<Config>("""
    {
        "host": "localhost",
        "port": 8080,
        "extra_field": "ignored"
    }
""")
println(config) // Config(host=localhost, port=8080)

9. @SerialName аннотация

Что делает: Задает кастомное имя поля в JSON (полезно для snake_case или сокращений).

@Serializable
data class Product(
    @SerialName("product_id")
    val id: Int,
    @SerialName("product_name")
    val name: String
)

val json = """{"product_id":1,"product_name":"Laptop"}"""
val product = Json.decodeFromString<Product>(json)
println(product) // Product(id=1, name=Laptop)

10. @Transient аннотация

Что делает: Исключает поле из процесса сериализации.

@Serializable
data class Account(
    val username: String,
    @Transient
    val password: String = ""
)

val account = Account("user123", "secret")
val json = Json.encodeToString(account)
println(json) // {"username":"user123"} — пароль не включен

Реальный практический пример: REST API клиент

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
import java.net.URL
import java.net.HttpURLConnection

@Serializable
data class Post(
    val userId: Int,
    val id: Int,
    val title: String,
    val body: String
)

@Serializable
data class CreatePostRequest(
    val userId: Int,
    val title: String,
    val body: String
)

class JsonPlaceholderClient(private val baseUrl: String = "https://jsonplaceholder.typicode.com") {
    
    private val json = Json { ignoreUnknownKeys = true }

    fun getPost(id: Int): Post {
        val url = URL("$baseUrl/posts/$id")
        val connection = url.openConnection() as HttpURLConnection
        connection.requestMethod = "GET"
        
        val response = connection.inputStream.bufferedReader().readText()
        return json.decodeFromString(response)
    }

    fun createPost(request: CreatePostRequest): Post {
        val url = URL("$baseUrl/posts")
        val connection = url.openConnection() as HttpURLConnection
        connection.requestMethod = "POST"
        connection.doOutput = true
        connection.setRequestProperty("Content-Type", "application/json")
        
        val requestBody = json.encodeToString(request)
        connection.outputStream.write(requestBody.toByteArray())
        
        val response = connection.inputStream.bufferedReader().readText()
        return json.decodeFromString(response)
    }
}

fun main() {
    val client = JsonPlaceholderClient()
    
    // Получаем пост
    val post = client.getPost(1)
    println("Получен пост: ${post.title}")
    
    // Создаем новый пост
    val newPost = client.createPost(
        CreatePostRequest(
            userId = 1,
            title = "Новый пост",
            body = "Содержимое нового поста"
        )
    )
    println("Создан пост с ID: ${newPost.id}")
}

Советы и типичные ошибки

Советы:

  • Всегда используйте @Serializable: Без этой аннотации компилятор выдаст ошибку.
  • Настройте Json один раз: Создайте экземпляр Json с нужными настройками и переиспользуйте его.
  • Используйте ignoreUnknownKeys = true: Для устойчивости к изменениям API.
  • Для вложенных объектов: Все вложенные классы также должны быть @Serializable.
  • Тестируйте сериализацию: Напишите unit-тесты для проверки корректности.

Типичные ошибки:

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