Что такое 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-тесты для проверки корректности.
Типичные ошибки:
- Ошибка: "Serializer for class 'X' is not found" — забудьте аннотацию @Serializable.
- Ошибка: "Unexpected JSON token" — несоответствие типов (ожидался Int, пришла строка).
- Ошибка: "Class 'X' is not registered for polymorphic serialization" — для sealed class нужно регистрировать подтипы.
- Проблемы с null: Если поле не nullable, а в JSON приходит null — будет исключение.
- Циклические ссылки:
Также в библиотеке
Рекомендаций