Spec-Zone.ru › Kotlin 2

Создание и использование сериализаторов

Сериализатор определяет структуру типа Kotlin в сериализованном виде, а реализации формата, например Json, управляют тем, как эта структура кодируется.

Diagram where a Kotlin value is serialized by a serializer into a sequence of primitives, encoded by a format into encoded data, decoded back into a sequence of primitives, and deserialized by a serializer into a Kotlin value

Сериализаторы определяют стратегии сериализации и десериализации типа с помощью интерфейса KSerializer. Kotlin serialization предоставляет сериализаторы для встроенных типов, коллекций и других типов.

С помощью этих сериализаторов можно сериализовать значения или изучать структуру их сериализованного представления, а пользовательские сериализаторы позволяют определить эту структуру самостоятельно.

Получение сериализаторов

Когда вы помечаете класс аннотацией @Serializable, плагин Kotlin serialization генерирует для этого класса KSerializer.

Чтобы получить этот автоматически сгенерированный сериализатор, вызовите сгенерированную функцию .serializer(). Сериализатор можно использовать напрямую с такими функциями, как Json.encodeToString(), или обратиться к его свойству descriptor, чтобы изучить структуру сериализованного представления типа.

Вот пример класса Color с одним целочисленным свойством и изучения структуры его сериализованного представления:

import kotlinx.serialization.*

//sampleStart
@Serializable
data class Color(val rgb: Int)

fun main() {
    // Retrieves the generated serializer for the Color class
    val colorSerializer: KSerializer<Color> = Color.serializer()

    println(colorSerializer.descriptor)
    // Color(rgb: kotlin.Int)
}
//sampleEnd

Встроенные примитивные типы и String также предоставляют сериализаторы с помощью функции .serializer(), например Int.serializer() и String.serializer().

Чтобы получить сериализатор для любого типа, в том числе параметризованного, можно использовать функцию верхнего уровня serializer<T>():

import kotlinx.serialization.*

//sampleStart
@Serializable
@SerialName("Color")
class Color(val rgb: Int)

fun main() {
    // Retrieves the serializer for the Map<String, Color>
    val stringToColorMapSerializer: KSerializer<Map<String, Color>> = serializer()

    // Prints: kotlin.collections.LinkedHashMap(PrimitiveDescriptor(kotlin.String), Color(rgb: kotlin.Int))
    println(stringToColorMapSerializer.descriptor)
}
//sampleEnd

Для обобщённых классов, если вы хотите использовать сгенерированную функцию .serializer(), передайте по одному аргументу KSerializer для каждого параметра типа:

import kotlinx.serialization.*

//sampleStart
@Serializable
@SerialName("Color")
class Color(val rgb: Int)

@Serializable
@SerialName("Box")
class Box<T>(val contents: T)    

fun main() {
    // Calls .serializer() using a KSerializer for the type parameter
    val boxedColorSerializer = Box.serializer(Color.serializer())

    println(boxedColorSerializer.descriptor)
    // Box(contents: Color)
}
//sampleEnd

Получение сериализаторов для типов коллекций

В отличие от классов, помеченных аннотацией @Serializable, для типов коллекций, таких как List<T>, не генерируется функция .serializer().

Чтобы получить сериализатор для типа коллекции, создайте его с помощью ListSerializer(), SetSerializer() или MapSerializer() и укажите сериализаторы для параметров типа коллекции.

Вот пример создания сериализатора для List<String> с помощью функции ListSerializer():

import kotlinx.serialization.*
import kotlinx.serialization.builtins.*

//sampleStart
fun main() {   
    val stringListSerializer: KSerializer<List<String>> = ListSerializer(String.serializer()) 
    println(stringListSerializer.descriptor)
    // kotlin.collections.ArrayList(PrimitiveDescriptor(kotlin.String))
}
//sampleEnd

Создание пользовательских сериализаторов

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

Как и сгенерированные сериализаторы, пользовательские сериализаторы определяют сериализацию и десериализацию типа с помощью интерфейса KSerializer. Для поддержки обеих операций KSerializer расширяет SerializationStrategy и DeserializationStrategy.

При сериализации в JSON можно также изменить выходные данные JSON, полученные от существующего сериализатора, не переопределяя их структуру с помощью JsonTransformingSerializer.

Создание пользовательского примитивного сериализатора

Используйте примитивный сериализатор, чтобы представить класс одним примитивным значением, например строкой или целым числом.

Чтобы создать пользовательский примитивный сериализатор:

  1. Создайте пользовательский сериализатор в виде object, реализующий KSerializer для сериализуемого класса:

    object YourSerializer : KSerializer<Type>
    
  2. Переопределите свойство descriptor, чтобы определить схему сериализованных данных.

    Используйте PrimitiveSerialDescriptor(serialName, kind), чтобы определить структуру сериализованного представления как одно примитивное значение. Укажите уникальный serialName, например полное имя, и используйте PrimitiveKind, соответствующий функциям кодирования и декодирования, используемым в сериализаторе:

    override val descriptor: SerialDescriptor =
        PrimitiveSerialDescriptor("com.example.Type", PrimitiveKind.STRING)
    

    Если descriptor не соответствует функциям кодирования и декодирования, изменения в kotlinx.serialization могут привести к непредсказуемому поведению сериализатора в некоторых форматах.

  3. Реализуйте функцию serialize(), чтобы определить, как значения преобразуются в сериализованный вид. Выберите функцию Encoder, соответствующую PrimitiveKind в descriptor:

    override fun serialize(encoder: Encoder, value: Type) {
        val encodedValue: String = // convert value to a primitive representation
        encoder.encodeString(encodedValue)
    }
    
  4. Реализуйте функцию deserialize(), чтобы определить, как преобразовать сериализованные данные обратно в экземпляр вашего класса. Decoder предоставляет функции для чтения данных, например decodeString().

    override fun deserialize(decoder: Decoder): Type {
        val decodedValue: String = decoder.decodeString()
        // Converts decodedValue back to Type
        return ...
    }
    
  5. Используйте аннотацию @Serializable, чтобы указать пользовательский сериализатор для класса:

    @Serializable(YourSerializer::class)
    data class Type(val stringValue: String)
    

Вот пример пользовательского примитивного сериализатора, который сериализует Color в виде шестнадцатеричной строки:

import kotlinx.serialization.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.json.*

//sampleStart
// Creates the custom serializer for the Color class
object ColorAsStringSerializer : KSerializer<Color> {
    // Defines the schema for the serialized data as a single string
    override val descriptor: SerialDescriptor =
        // Specifies a unique name and a PrimitiveKind
        PrimitiveSerialDescriptor("my.app.Color", PrimitiveKind.STRING)

    // Defines how a Color value is serialized as a string
    override fun serialize(encoder: Encoder, value: Color) {
        // Converts the RGB value to a hexadecimal string
        val hexValue = value.rgb.toString(16).padStart(6, '0')
        // Encodes the serialized value as a string
        encoder.encodeString(hexValue)
    }

    // Defines how a Color value is deserialized from a string
    override fun deserialize(decoder: Decoder): Color {
        // Decodes the serialized string value
        val hexValue = decoder.decodeString()
        // Converts the decoded value back into a Color
        return Color(hexValue.toInt(16))
    }
}
// Specifies ColorAsStringSerializer as the custom serializer for the Color class
@Serializable(ColorAsStringSerializer::class)
data class Color(val rgb: Int)

fun main() {
    val color = Color(0x00FF00)
    // Serializes a Color value to JSON
    val jsonString = Json.encodeToString(color)

    println(jsonString)
    // "00ff00"

    // Deserializes the JSON string into a Color value
    val deserializedColor = Json.decodeFromString<Color>(jsonString)

    println(deserializedColor.rgb)
    // 65280
}
//sampleEnd

Сериализация двоичных данных в виде строк Base64

Пользовательский примитивный сериализатор подходит для решения распространённых задач сериализации, выходящих за рамки простого преобразования значений. Одна из таких задач — представление двоичных данных в виде строки Base64.

В разных API по умолчанию используются разные варианты Base64. Выберите кодировщик Base64 для Kotlin, соответствующий ожидаемому варианту, например Base64.Default или Base64.Mime.

Вот пример сериализации ByteArray в виде строки Base64 с помощью Base64.Default в формате JSON:

import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.descriptors.*
import kotlin.io.encoding.*

//sampleStart
// Creates a custom primitive serializer,
// which represents ByteArray as a Base64 string
object ByteArrayAsBase64Serializer : KSerializer<ByteArray> {

    // Uses the default Base64 variant
    private val base64 = Base64.Default

    // Defines the serialized form as a single STRING primitive
    override val descriptor: SerialDescriptor
        get() = PrimitiveSerialDescriptor(
            "ByteArrayAsBase64Serializer",
            PrimitiveKind.STRING
        )

    // Encodes the ByteArray as a Base64 string
    override fun serialize(encoder: Encoder, value: ByteArray) {
        val base64Encoded = base64.encode(value)
        encoder.encodeString(base64Encoded)
    }

    // Decodes the Base64 string back into a ByteArray
    override fun deserialize(decoder: Decoder): ByteArray {
        val base64Decoded = decoder.decodeString()
        return base64.decode(base64Decoded)
    }
}

@Serializable
data class Value(
    // Specifies the custom serializer for this property
    @Serializable(ByteArrayAsBase64Serializer::class)
    val base64Input: ByteArray
) {

    // Implements value-based equality for ByteArray
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false
        other as Value
        return base64Input.contentEquals(other.base64Input)
    }

    // Computes hashCode based on array contents
    override fun hashCode(): Int {
        return base64Input.contentHashCode()
    }
}

fun main() {
    val string = "PNG_IMAGE_DATA"
    val value = Value(string.toByteArray())

    // Serializes Value to JSON
    val encoded = Json.encodeToString(value)

    println(encoded)
    // {"base64Input":"UE5HX0lNQUdFX0RBVEE="}

    // Deserializes JSON back into Value
    val decoded = Json.decodeFromString<Value>(encoded)

    println(decoded.base64Input.decodeToString())
    // PNG_IMAGE_DATA
}
//sampleEnd

Передача сериализации другому сериализатору

Можно сериализовать класс как другой тип, передав логику сериализации сериализатору этого типа. Например, такой подход позволяет сериализовать класс как непримитивный тип, например IntArray.

Чтобы передать сериализацию другому сериализатору, создайте пользовательский сериализатор, определяющий свойство для сериализатора делегируемого типа:

  1. Переопределите свойство descriptor, чтобы обернуть descriptor делегируемого сериализатора.

    При передаче сериализации другому сериализатору нельзя напрямую использовать исходный descriptor класса или descriptor делегируемого типа. Вместо этого создайте новый descriptor, повторно использующий структуру делегируемого сериализатора.

  2. Переопределите функцию serialize(), чтобы преобразовать экземпляр вашего класса в делегируемый тип, и используйте функцию encoder.encodeSerializableValue(), чтобы закодировать его с помощью делегируемого сериализатора.

  3. Переопределите функцию deserialize(), чтобы использовать функцию decoder.decodeSerializableValue() с делегируемым сериализатором для декодирования значения и его преобразования обратно в экземпляр вашего класса.

Вот пример сериализации класса Color как IntArray с передачей логики сериализации IntArraySerializer:

import kotlinx.serialization.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.builtins.IntArraySerializer
import kotlinx.serialization.json.*

//sampleStart
// Creates a custom serializer that delegates to IntArraySerializer
class ColorIntArraySerializer : KSerializer<Color> {
    private val delegateSerializer = IntArraySerializer()
    override val descriptor = SerialDescriptor("my.app.Color", delegateSerializer.descriptor)

    // Delegates serialization logic to IntArraySerializer
    override fun serialize(encoder: Encoder, value: Color) {
        val data = intArrayOf(
            (value.rgb shr 16) and 0xFF,
            (value.rgb shr 8) and 0xFF,
            value.rgb and 0xFF
        )
        encoder.encodeSerializableValue(delegateSerializer, data)
    }

    // Delegates deserialization and converts IntArray back to Color
    override fun deserialize(decoder: Decoder): Color {
        val array = decoder.decodeSerializableValue(delegateSerializer)
        return Color((array[0] shl 16) or (array[1] shl 8) or array[2])
    }
}

@Serializable(ColorIntArraySerializer::class)
class Color(val rgb: Int)

fun main() {
    val green = Color(0x00ff00)

    println(Json.encodeToString(green))
    // [0,255,0]
}
//sampleEnd

Хотя представление в виде массива не является общепринятым для JSON, оно может уменьшить размер сериализованных данных при использовании с ByteArray и двоичным форматом.

Подробнее о том, как форматы сериализации, отличные от JSON, обрабатывают массивы, см. в разделе Альтернативные и пользовательские форматы сериализации.

Сериализация классов с помощью класса-заместителя

Класс-заместитель — это класс, соответствующий сериализованному представлению другого класса. Его можно использовать, если нужно:

  • Изменить способ сериализации класса, не изменяя сам класс.

  • Избежать создания составного сериализатора.

  • Проверить сериализованное представление перед созданием исходного класса.

  • Обработать случаи, когда прямая сериализация не соответствует правилам класса.

Классы-заместители можно сделать private и использовать блок init, чтобы задать ограничения на сериализованное представление класса. Кроме того, можно задать собственное сериализованное имя, чтобы сохранить неизменным имя сериализованного типа.

Рассмотрим пример сериализации Color в виде объекта JSON с определёнными диапазонами значений свойств r, g и b:

// Defines a surrogate that matches the serialized form
@Serializable
@SerialName("Color")
private class ColorSurrogate(val r: Int, val g: Int, val b: Int) {
    init {
        // Enforces constraints on the serialized form
        require(r in 0..255 && g in 0..255 && b in 0..255)
    }
}

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

Классы-заместители определяют SerialDescriptor с собственным serialName. Можно повторно использовать структуру SerialDescriptor класса-заместителя, но не его serialName, поскольку у каждого SerialDescriptor должен быть уникальный serialName.

Чтобы получить сгенерированный сериализатор класса-заместителя, используйте ColorSurrogate.serializer():

object ColorSerializer : KSerializer<Color> {
    // The serialNames of descriptors must be unique
    override val descriptor: SerialDescriptor = SerialDescriptor("my.app.Color", ColorSurrogate.serializer().descriptor)

    // Converts the original class to the surrogate representation
    override fun serialize(encoder: Encoder, value: Color) {
        val surrogate = ColorSurrogate((value.rgb shr 16) and 0xff, (value.rgb shr 8) and 0xff, value.rgb and 0xff)
        encoder.encodeSerializableValue(ColorSurrogate.serializer(), surrogate)
    }

    // Converts the surrogate representation back to the original class
    override fun deserialize(decoder: Decoder): Color {
        val surrogate = decoder.decodeSerializableValue(ColorSurrogate.serializer())
        return Color((surrogate.r shl 16) or (surrogate.g shl 8) or surrogate.b)
    }
}

Наконец, укажите пользовательский сериализатор для класса:

import kotlinx.serialization.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.builtins.IntArraySerializer
import kotlinx.serialization.json.*

// Defines a private surrogate class with custom properties
@Serializable
@SerialName("Color")
private class ColorSurrogate(val r: Int, val g: Int, val b: Int) {
    init {
        // Enforces constraints on the serialized form
        require(r in 0..255 && g in 0..255 && b in 0..255)
    }
}

// Creates a custom serializer that converts to and from the surrogate
object ColorSerializer : KSerializer<Color> {
    // Defines a unique serialName for the wrapped SerialDescriptor
    override val descriptor: SerialDescriptor =
        SerialDescriptor("my.app.Color", ColorSurrogate.serializer().descriptor)

    // Converts the original class to the surrogate representation
    override fun serialize(encoder: Encoder, value: Color) {
        val surrogate = ColorSurrogate((value.rgb shr 16) and 0xff, (value.rgb shr 8) and 0xff, value.rgb and 0xff)
        encoder.encodeSerializableValue(ColorSurrogate.serializer(), surrogate)
    }

    // Converts the surrogate representation back to the original class
    override fun deserialize(decoder: Decoder): Color {
        val surrogate = decoder.decodeSerializableValue(ColorSurrogate.serializer())
        return Color((surrogate.r shl 16) or (surrogate.g shl 8) or surrogate.b)
    }
}

//sampleStart
// Specifies ColorSerializer as the custom serializer for the class
@Serializable(ColorSerializer::class)
class Color(val rgb: Int)

fun main() {
    val green = Color(0x00ff00)

    println(Json.encodeToString(green))
    // {"r":0,"g":255,"b":0}
}
//sampleEnd

Создание пользовательского составного сериализатора

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

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

Чтобы создать пользовательский составной сериализатор:

  1. Создайте сериализатор в виде object, реализующий KSerializer для целевого класса:

    object YourSerializer : KSerializer<Type>
    
  2. Переопределите свойство descriptor и определите схему сериализации с помощью функции buildClassSerialDescriptor():

    override val descriptor: SerialDescriptor =
        buildClassSerialDescriptor("com.example.Type") {
            // ...
        }
    
  3. Укажите каждое свойство в функции buildClassSerialDescriptor() с помощью функции element(). Порядок элементов определяет их индексы, начиная с 0.

    SerialKind сериализатора descriptor определяет, что представляет каждый element(). В descriptor класса element() представляет свойство, а в descriptor перечисления element() представляет константы, например RED или GREEN:

    override val descriptor: SerialDescriptor =
        buildClassSerialDescriptor("com.example.Type") {
            element<Int>("first")
            element<Int>("second")
        }
    
  4. Реализуйте функцию serialize() с помощью DSL encodeStructure(). Внутри блока её получателем лямбды является CompositeEncoder. С его помощью вызывайте для каждого поля такие функции, как encodeIntElement(), соблюдая порядок, заданный в descriptor:

    override fun serialize(encoder: Encoder, value: Type) =
        encoder.encodeStructure(descriptor) {
            encodeIntElement(descriptor, 0, value.first)
            encodeIntElement(descriptor, 1, value.second)
        }
    
  5. Реализуйте функцию deserialize() с помощью DSL decodeStructure(). Внутри блока её получателем лямбды является CompositeDecoder. С его помощью вызывайте такие функции, как decodeIntElement(), чтобы декодировать каждое свойство.

    Большинство форматов допускают кодирование данных в произвольном порядке, который может отличаться от порядка элементов в descriptor сериализатора. Используйте функцию decodeElementIndex(), чтобы определить, какой element() нужно декодировать. Когда элементов больше не остаётся, функция возвращает CompositeDecoder.DECODE_DONE; используйте это значение, чтобы остановить декодирование текущей структуры:

    override fun deserialize(decoder: Decoder): Type =
        decoder.decodeStructure(descriptor) {
            var first = 0
            var second = ""
    
            // Uses decodeElementIndex to ensure correct decoding regardless of order
            while (true) {
                when (val index = decodeElementIndex(descriptor)) {
                    0 -> first = decodeStringElement(descriptor, 0)
                    1 -> second = decodeIntElement(descriptor, 1)
                    CompositeDecoder.DECODE_DONE -> break
                    else -> error("Unexpected index: $index")
                }
            }
    
            Type(first, second)
        }
    
  6. Используйте аннотацию @Serializable(YourSerializer::class), чтобы указать пользовательский сериализатор для класса.

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

Рассмотрим пример сериализации класса Color с несколькими свойствами:

import kotlinx.serialization.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.json.*

//sampleStart
// Creates a custom serializer for the Color class with multiple properties
object ColorAsObjectSerializer : KSerializer<Color> {
    // Defines the schema for the Color class
    override val descriptor: SerialDescriptor =
        buildClassSerialDescriptor("my.app.Color") {
            // Specifies each property with its type and name with the element() function
            element<Int>("r")
            element<Int>("g")
            element<Int>("b")
        }

    // Serializes the Color in the order specified in the descriptor
    override fun serialize(encoder: Encoder, value: Color) =
        encoder.encodeStructure(descriptor) {
            encodeIntElement(descriptor, 0, (value.rgb shr 16) and 0xff)
            encodeIntElement(descriptor, 1, (value.rgb shr 8) and 0xff)
            encodeIntElement(descriptor, 2, value.rgb and 0xff)
        }

    // Deserializes the data back into a Color object
    override fun deserialize(decoder: Decoder): Color =
        decoder.decodeStructure(descriptor) {
            // Temporary variables to hold the decoded values
            var r = -1
            var g = -1
            var b = -1
            // Uses decodeElementIndex() since element order may vary by format
            while (true) {
                when (val index = decodeElementIndex(descriptor)) {
                    0 -> r = decodeIntElement(descriptor, 0)
                    1 -> g = decodeIntElement(descriptor, 1)
                    2 -> b = decodeIntElement(descriptor, 2)
                    CompositeDecoder.DECODE_DONE -> break
                    else -> error("Unexpected index: $index")
                }
            }
            // Validates values and reconstructs Color
            require(r in 0..255 && g in 0..255 && b in 0..255)
            Color((r shl 16) or (g shl 8) or b)
        }
}

// Specifies the custom serializer for Color
@Serializable(ColorAsObjectSerializer::class)
data class Color(val rgb: Int)

fun main() {
    val color = Color(0x00ff00)
    val string = Json.encodeToString(color)

    println(string)
    // {"r":0,"g":255,"b":0}

    require(Json.decodeFromString<Color>(string) == color)
}
//sampleEnd

Кодирование значений по умолчанию в пользовательских сериализаторах

Сериализаторы, сгенерированные плагином, проверяют, нужно ли кодировщику кодировать значения, равные значениям по умолчанию. Например, в JSON это поведение управляется свойством encodeDefaults.

Чтобы добиться такого же поведения в пользовательском сериализаторе, используйте функцию shouldEncodeElementDefault(), передав ей descriptor сериализатора и индекс кодируемого элемента.

Если свойство помечено аннотацией @EncodeDefault, сериализаторы, сгенерированные плагином, не вызывают shouldEncodeElementDefault().

Вот пример пользовательского сериализатора, который кодирует значения Color по умолчанию, если включён параметр encodeDefaults:

import kotlinx.serialization.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.json.*

//sampleStart
// Creates a custom serializer for the Color class with multiple properties
object ColorAsObjectSerializer : KSerializer<Color> {
    // Defines the schema for the Color class
    override val descriptor: SerialDescriptor =
        buildClassSerialDescriptor("my.app.Color") {
            // Specifies each property with its type and name with the element() function
            element<Int>("r", isOptional = true)
            element<Int>("g", isOptional = true)
            element<Int>("b", isOptional = true)
        }
   
    override fun serialize(encoder: Encoder, value: Color) =
        encoder.encodeStructure(descriptor) {
            val r = (value.rgb shr 16) and 0xff
            val g = (value.rgb shr 8) and 0xff
            val b = value.rgb and 0xff

            // Encodes r if it differs from its default value,
            // or if the encoder needs to encode the first element's default value
            if (r != 0 || shouldEncodeElementDefault(descriptor, 0)) {
                encodeIntElement(descriptor, 0, r)
            }
            if (g != 255 || shouldEncodeElementDefault(descriptor, 1)) {
                encodeIntElement(descriptor, 1, g)
            }
            if (b != 0 || shouldEncodeElementDefault(descriptor, 2)) {
                encodeIntElement(descriptor, 2, b)
            }
        }

    // Deserializes the data back into a Color object
    override fun deserialize(decoder: Decoder): Color =
        decoder.decodeStructure(descriptor) {
            var r = 0
            var g = 255
            var b = 0

            while (true) {
                when (val index = decodeElementIndex(descriptor)) {
                    0 -> r = decodeIntElement(descriptor, 0)
                    1 -> g = decodeIntElement(descriptor, 1)
                    2 -> b = decodeIntElement(descriptor, 2)
                    CompositeDecoder.DECODE_DONE -> break
                    else -> error("Unexpected index: $index")
                }
            }
            require(r in 0..255 && g in 0..255 && b in 0..255)
            Color((r shl 16) or (g shl 8) or b)
        }
}

// Specifies the custom serializer for Color
@Serializable(ColorAsObjectSerializer::class)
data class Color(val rgb: Int = 0x00ff00)

fun main() {
    val color = Color()
    val stringWithDefaults = Json { encodeDefaults = true }.encodeToString(color)
   
    println(stringWithDefaults)
    // {"r":0,"g":255,"b":0}
}
//sampleEnd

Оптимизация десериализации с помощью последовательного декодирования

В некоторых форматах используется строго упорядоченная схема, что позволяет выполнять последовательное декодирование. Для таких форматов можно оптимизировать десериализацию с помощью функции decodeSequentially(). Эта функция возвращает true, если текущую структуру можно декодировать по порядку. Обработайте этот случай отдельно, чтобы избежать более сложной логики декодирования отдельных элементов не по порядку.

Сериализаторы, сгенерированные плагином kotlinx.serialization, используют эту оптимизацию.

Вот пример использования decodeSequentially() для оптимизации десериализации, когда это возможно:

import kotlinx.serialization.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.json.*


object ColorAsObjectSerializer : KSerializer<Color> {

    override val descriptor: SerialDescriptor =
        buildClassSerialDescriptor("my.app.Color") {
            element<Int>("r")
            element<Int>("g")
            element<Int>("b")
        }

    override fun serialize(encoder: Encoder, value: Color) =
        encoder.encodeStructure(descriptor) {
            encodeIntElement(descriptor, 0, (value.rgb shr 16) and 0xff)
            encodeIntElement(descriptor, 1, (value.rgb shr 8) and 0xff)
            encodeIntElement(descriptor, 2, value.rgb and 0xff)
        }

//sampleStart
    override fun deserialize(decoder: Decoder): Color =
        decoder.decodeStructure(descriptor) {
            var r = -1
            var g = -1
            var b = -1
            // Decodes values directly in order if the format stores data sequentially
            @OptIn(ExperimentalSerializationApi::class)
            if (decodeSequentially()) {
                r = decodeIntElement(descriptor, 0)           
                g = decodeIntElement(descriptor, 1)  
                b = decodeIntElement(descriptor, 2)
            } else while (true) {
                // Ensures correct decoding for formats where elements may be unordered
                when (val index = decodeElementIndex(descriptor)) {
                    0 -> r = decodeIntElement(descriptor, 0)
                    1 -> g = decodeIntElement(descriptor, 1)
                    2 -> b = decodeIntElement(descriptor, 2)
                    CompositeDecoder.DECODE_DONE -> break
                    else -> error("Unexpected index: $index")
                }
            }
            require(r in 0..255 && g in 0..255 && b in 0..255)
            Color((r shl 16) or (g shl 8) or b)
        }
}
//sampleEnd

@Serializable(ColorAsObjectSerializer::class)
data class Color(val rgb: Int)

fun main() {
    val color = Color(0x00ff00)
    val string = Json.encodeToString(color)

    println(string)
    // {"r":0,"g":255,"b":0}

    require(Json.decodeFromString<Color>(string) == color)
}

Создание пользовательского сериализатора для обобщённых типов

Чтобы создать пользовательский сериализатор для обобщённого класса, объявите сериализатор как class, а не как object, и укажите один параметр конструктора KSerializer для каждого параметра обобщённого типа.

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

Рассмотрим пример обобщённого класса Box<T>:

import kotlinx.serialization.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.json.*

//sampleStart
@Serializable(BoxSerializer::class)
data class Box<T>(val contents: T)

// Creates a custom serializer as a class for Box<T>
class BoxSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<Box<T>> {
    // Defines a unique serialName for the Box<T> descriptor
    override val descriptor: SerialDescriptor =
        SerialDescriptor("my.app.Box", dataSerializer.descriptor)

    // Delegates serialization and deserialization
    override fun serialize(encoder: Encoder, value: Box<T>) = dataSerializer.serialize(encoder, value.contents)
    override fun deserialize(decoder: Decoder) = Box(dataSerializer.deserialize(decoder))
}

@Serializable
data class Project(val name: String)

fun main() {
    val box = Box(Project("kotlinx.serialization"))
    val string = Json.encodeToString(box)

    println(string)
    // {"name":"kotlinx.serialization"}

    println(Json.decodeFromString<Box<Project>>(string))
    // Box(contents=Project(name=kotlinx.serialization))
}
//sampleEnd

Использование сгенерированного плагином сериализатора вместе с пользовательским

По умолчанию плагин Kotlin serialization не генерирует сериализатор, если вы указали пользовательский сериализатор с помощью @Serializable(YourSerializer::class).

Однако сгенерированный плагином сериализатор всё ещё может понадобиться, например, для таких целей:

  • Использование сгенерированного плагином сериализатора в качестве резервной стратегии.

  • Изучение сгенерированного плагином descriptor для доступа к структуре по умолчанию.

  • Повторное использование поведения сериализации по умолчанию в подклассах, для которых не задан пользовательский сериализатор.

Чтобы сохранить автоматически сгенерированный сериализатор наряду с пользовательским, пометьте сериализуемый класс аннотацией @KeepGeneratedSerializer. Чтобы получить сгенерированный плагином сериализатор, используйте функцию .generatedSerializer() объекта-компаньона сериализуемого класса.

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

Вот пример использования пользовательского сериализатора вместе со сгенерированным плагином сериализатором:

import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*

object ColorAsStringSerializer : KSerializer<Color> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("my.app.ColorAsString", PrimitiveKind.STRING)

    override fun serialize(encoder: Encoder, value: Color) {
        val string = value.rgb.toString(16).padStart(6, '0')
        encoder.encodeString(string)
    }

    override fun deserialize(decoder: Decoder): Color {
        val string = decoder.decodeString()
        return Color(string.toInt(16))
    }
}

//sampleStart
@OptIn(ExperimentalSerializationApi::class)
@KeepGeneratedSerializer
@Serializable(ColorAsStringSerializer::class)
class Color(val rgb: Int)

fun main() {
    val green = Color(0x00ff00)

    // Uses the custom serializer
    println(Json.encodeToString(green))
    // "00ff00"

    // Uses the plugin-generated serializer
    println(Json.encodeToString(Color.generatedSerializer(), green))
    // {"rgb":65280}

}
//sampleEnd

Применение сериализаторов

Вы можете применять пользовательские сериализаторы к собственным классам и типам сторонних библиотек. Типы сторонних библиотек, например java.util.Date, нельзя напрямую аннотировать с помощью @Serializable, поскольку их исходный код нельзя изменить.

Передача сериализатора вручную

Чтобы сериализовать тип с помощью пользовательского сериализатора, создайте сериализатор и явно передайте его перегруженным вариантам таких функций, как Json.encodeToString() и Json.decodeFromString().

Вот пример сериализации значений Date в виде количества миллисекунд, прошедших с начала эпохи Unix:

import kotlinx.serialization.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.json.*
import java.util.Date
import java.text.SimpleDateFormat

//sampleStart
// Can't use @Serializable on Date without access to its source code
object DateAsLongSerializer : KSerializer<Date> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("my.app.DateAsLong", PrimitiveKind.LONG)
    override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time)
    override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong())
}

fun main() {
    val kotlin10ReleaseDate = SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00") 

    // Serializes Date as a Long in milliseconds
    println(Json.encodeToString(DateAsLongSerializer, kotlin10ReleaseDate))    
    // 1455494400000
}
//sampleEnd

Указание сериализатора для свойства

Если тип используется как свойство сериализуемого класса, укажите его пользовательский сериализатор для этого свойства с помощью аннотации @Serializable:

import kotlinx.serialization.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.json.*
import java.util.Date
import java.text.SimpleDateFormat

object DateAsLongSerializer : KSerializer<Date> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("my.app.DateAsLong", PrimitiveKind.LONG)
    override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time)
    override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong())
}

//sampleStart
@Serializable
class ProgrammingLanguage(
    val name: String,
    // Specifies the custom serializer for the Date property
    @Serializable(DateAsLongSerializer::class)
    val stableReleaseDate: Date
)

fun main() {
    val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00"))

    println(Json.encodeToString(data))
    // {"name":"Kotlin","stableReleaseDate":1455494400000}
}
//sampleEnd

Указание сериализатора для типа

Вы также можете применить аннотацию @Serializable непосредственно к типу. Это позволяет указать пользовательский сериализатор для типа, когда он используется в качестве аргумента обобщённого типа, например в List<Date>:

import kotlinx.serialization.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.json.*
import java.util.Date
import java.text.SimpleDateFormat

object DateAsLongSerializer : KSerializer<Date> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("my.app.DateAsLong", PrimitiveKind.LONG)
    override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time)
    override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong())
}

//sampleStart
@Serializable          
class ProgrammingLanguage(
    val name: String,
     // Specifies the custom serializer for Date as a generic type argument
    val releaseDates: List<@Serializable(DateAsLongSerializer::class) Date>
)

fun main() {
    val df = SimpleDateFormat("yyyy-MM-ddX")
    val data = ProgrammingLanguage("Kotlin", listOf(df.parse("2023-07-06+00"), df.parse("2023-04-25+00"), df.parse("2022-12-28+00")))
 
   println(Json.encodeToString(data))
    // {"name":"Kotlin","releaseDates":[1688601600000,1682380800000,1672185600000]}
}
//sampleEnd

Указание сериализатора для файла

Чтобы применить сериализатор ко всем свойствам заданного типа в исходном файле, добавьте аннотацию @UseSerializers в начало файла:

@file:UseSerializers(DateAsLongSerializer::class)

Это применяет DateAsLongSerializer ко всем экземплярам этого типа в файле, поэтому вам не нужно аннотировать каждое свойство отдельно.

Вот пример:

// Applies the custom serializer to all properties of that type in the file
@file:UseSerializers(DateAsLongSerializer::class)

import kotlinx.serialization.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.json.*
import java.util.Date
import java.text.SimpleDateFormat

object DateAsLongSerializer : KSerializer<Date> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("my.app.DateAsLong", PrimitiveKind.LONG)
    override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time)
    override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong())
}

// Uses the file-level serializer for the Date property
@Serializable
class ProgrammingLanguage(val name: String, val stableReleaseDate: Date)

fun main() {
    val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00"))
 
   println(Json.encodeToString(data))
    // {"name":"Kotlin","stableReleaseDate":1455494400000}
}

Указание сериализаторов с помощью псевдонимов типов

В Kotlin Serialization стратегии сериализации обычно задаются явно с помощью аннотации @Serializable. Глобальная настройка сериализатора не предусмотрена, за исключением контекстной сериализации.

Если вы многократно используете один и тот же сериализатор в разных местах, можно определить typealias с прикреплённой аннотацией сериализатора.

Это позволяет повторно использовать аннотированный тип, не добавляя аннотацию @Serializable в каждом месте использования.

Вот пример использования typealias для применения DateAsLongSerializer и DateAsSimpleTextSerializer к Date:

import kotlinx.serialization.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.json.*
import java.util.Date
import java.text.SimpleDateFormat
import java.util.TimeZone

//sampleStart
object DateAsLongSerializer : KSerializer<Date> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("my.app.DateAsLong", PrimitiveKind.LONG)
    override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time)
    override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong())
}

// Defines a serializer that encodes Date as a formatted string (yyyy-MM-dd)
object DateAsSimpleTextSerializer: KSerializer<Date> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("my.app.DateAsSimpleText", PrimitiveKind.LONG)
    private val format = SimpleDateFormat("yyyy-MM-dd").apply {
        // Sets the time zone to UTC for consistent output
        setTimeZone(TimeZone.getTimeZone("UTC"))
    }
    override fun serialize(encoder: Encoder, value: Date) = encoder.encodeString(format.format(value))
    override fun deserialize(decoder: Decoder): Date = format.parse(decoder.decodeString())
}

// Applies global serializers using typealias to avoid annotating each occurrence
typealias DateAsLong = @Serializable(DateAsLongSerializer::class) Date

typealias DateAsText = @Serializable(DateAsSimpleTextSerializer::class) Date

// Uses typealiases to apply custom serializers for Date properties
@Serializable          
class ProgrammingLanguage(val stableReleaseDate: DateAsText, val lastReleaseTimestamp: DateAsLong)

fun main() {
    val format = SimpleDateFormat("yyyy-MM-ddX")
    val data = ProgrammingLanguage(format.parse("2016-02-15+00"), format.parse("2022-07-07+00"))

    println(Json.encodeToString(data))
    // {"stableReleaseDate":"2016-02-15","lastReleaseTimestamp":1657152000000}
}
//sampleEnd

Реализация контекстной сериализации

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

Например, с помощью контекстной сериализации можно сериализовать java.util.Date в формате JSON либо как String ISO 8601, либо как Long — в зависимости от используемой версии протокола. Этот подход поддерживается встроенным классом ContextualSerializer.

При контекстной сериализации пользовательский сериализатор для типа выбирается во время выполнения из SerializersModule.

Чтобы реализовать контекстную сериализацию:

  1. Пометьте свойство сериализуемого класса аннотацией @Contextual.

    Аннотация @Contextual — это сокращённая запись для класса ContextualSerializer. Чтобы применить контекстную сериализацию к нескольким свойствам в одном файле, используйте аннотацию @UseContextualSerialization.

  2. Создайте SerializersModule с помощью функции построителя SerializersModule(). Используйте функцию contextual(), чтобы зарегистрировать пользовательский сериализатор для контекстной сериализации.

    Если SerializersModule отсутствует, при сериализации или десериализации типов с контекстными аннотациями, для которых не предусмотрен сериализатор по умолчанию, возникает исключение SerializationException.

  3. Создайте экземпляр Json и передайте SerializersModule свойству serializersModule.

Вот пример:

import kotlinx.serialization.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.json.*
import java.util.Date
import java.text.SimpleDateFormat

//sampleStart
// Creates a custom serializer for Date
object DateAsLongSerializer : KSerializer<Date> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("my.app.Date", PrimitiveKind.LONG)
    override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time)
    override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong())
}

@Serializable
class ProgrammingLanguage(
    val name: String,
    // Specifies contextual serialization for Date
    @Contextual
    val stableReleaseDate: Date
)

// Defines a SerializersModule and registers the contextual serializer for Date
private val module = SerializersModule { 
    contextual(DateAsLongSerializer)
}

// Creates a Json instance with the custom SerializersModule
val format = Json { serializersModule = module }

fun main() {
    val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00"))
 
   println(format.encodeToString(data))
    // {"name":"Kotlin","stableReleaseDate":1455494400000}
}
//sampleEnd

Контекстная сериализация обобщённых классов

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

Нельзя использовать один экземпляр сериализатора для обобщённых классов, поскольку аргументы обобщённых типов могут различаться. Например, следующий подход работает только для Box<Int>, но не для других типов, таких как Box<String>:

val incorrectModule = SerializersModule {
    // This only works for Box<Int>, but not for Box<String> or other types
    contextual(BoxSerializer(Int.serializer()))
}

Вместо этого зарегистрируйте функцию, которая создаёт сериализатор для обобщённых типов, например Box<T>, на основе сериализаторов их аргументов типа. Вот пример:

val correctModule = SerializersModule {
    // args[0] is the serializer for T,
    // for example Int.serializer() or String.serializer()
    contextual(Box::class) { args -> BoxSerializer(args[0]) }
}

Можно объединить несколько экземпляров SerializersModule с помощью оператора plus, например модули для обобщённых и необобщённых классов. Подробнее см. в разделе Объединение нескольких экземпляров SerializersModule.

Дальнейшие шаги

  • Узнайте, как преобразовывать структуру JSON, изменяя дерево элементов JSON вместо создания пользовательского сериализатора.

  • Узнайте о альтернативных и пользовательских форматах сериализации, позволяющих реализовать специфичные для формата представления ваших данных.

16 июня 2026 г.
Сериализация полиморфных классовБиблиотека Kotlin Metadata JVM

© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/serialization-create-and-use-serializers.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API