Создание и использование сериализаторов
Сериализатор определяет структуру типа Kotlin в сериализованном виде, а реализации формата, например Json, управляют тем, как эта структура кодируется.
Сериализаторы определяют стратегии сериализации и десериализации типа с помощью интерфейса 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
Чтобы получить сериализатор для любого типа, в том числе параметризованного, можно использовать функцию верхнего уровня 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.
Создание пользовательского примитивного сериализатора
Используйте примитивный сериализатор, чтобы представить класс одним примитивным значением, например строкой или целым числом.
Чтобы создать пользовательский примитивный сериализатор:
-
Создайте пользовательский сериализатор в виде
object, реализующийKSerializerдля сериализуемого класса:object YourSerializer : KSerializer<Type>
-
Переопределите свойство
descriptor, чтобы определить схему сериализованных данных.Используйте
PrimitiveSerialDescriptor(serialName, kind), чтобы определить структуру сериализованного представления как одно примитивное значение. Укажите уникальныйserialName, например полное имя, и используйтеPrimitiveKind, соответствующий функциям кодирования и декодирования, используемым в сериализаторе:override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("com.example.Type", PrimitiveKind.STRING) -
Реализуйте функцию
serialize(), чтобы определить, как значения преобразуются в сериализованный вид. Выберите функциюEncoder, соответствующуюPrimitiveKindвdescriptor:override fun serialize(encoder: Encoder, value: Type) { val encodedValue: String = // convert value to a primitive representation encoder.encodeString(encodedValue) } -
Реализуйте функцию
deserialize(), чтобы определить, как преобразовать сериализованные данные обратно в экземпляр вашего класса.Decoderпредоставляет функции для чтения данных, напримерdecodeString().override fun deserialize(decoder: Decoder): Type { val decodedValue: String = decoder.decodeString() // Converts decodedValue back to Type return ... } -
Используйте аннотацию
@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.
Чтобы передать сериализацию другому сериализатору, создайте пользовательский сериализатор, определяющий свойство для сериализатора делегируемого типа:
-
Переопределите свойство
descriptor, чтобы обернутьdescriptorделегируемого сериализатора. Переопределите функцию
serialize(), чтобы преобразовать экземпляр вашего класса в делегируемый тип, и используйте функциюencoder.encodeSerializableValue(), чтобы закодировать его с помощью делегируемого сериализатора.Переопределите функцию
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 и двоичным форматом.
Сериализация классов с помощью класса-заместителя
Класс-заместитель — это класс, соответствующий сериализованному представлению другого класса. Его можно использовать, если нужно:
Изменить способ сериализации класса, не изменяя сам класс.
Избежать создания составного сериализатора.
Проверить сериализованное представление перед созданием исходного класса.
Обработать случаи, когда прямая сериализация не соответствует правилам класса.
Классы-заместители можно сделать 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 этого представления.
Чтобы получить сгенерированный сериализатор класса-заместителя, используйте 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
Создание пользовательского составного сериализатора
Составные сериализаторы можно использовать для представления сложных структур данных, например классов с несколькими свойствами.
В отличие от использования класса-заместителя, пользовательский составной сериализатор позволяет напрямую определить сериализованную структуру исходного класса без дополнительного этапа преобразования. В некоторых случаях это также повышает производительность, но требует вручную написать больше логики сериализации.
Чтобы создать пользовательский составной сериализатор:
-
Создайте сериализатор в виде
object, реализующийKSerializerдля целевого класса:object YourSerializer : KSerializer<Type>
-
Переопределите свойство
descriptorи определите схему сериализации с помощью функцииbuildClassSerialDescriptor():override val descriptor: SerialDescriptor = buildClassSerialDescriptor("com.example.Type") { // ... } -
Укажите каждое свойство в функции
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") } -
Реализуйте функцию
serialize()с помощью DSLencodeStructure(). Внутри блока её получателем лямбды являетсяCompositeEncoder. С его помощью вызывайте для каждого поля такие функции, какencodeIntElement(), соблюдая порядок, заданный вdescriptor:override fun serialize(encoder: Encoder, value: Type) = encoder.encodeStructure(descriptor) { encodeIntElement(descriptor, 0, value.first) encodeIntElement(descriptor, 1, value.second) } -
Реализуйте функцию
deserialize()с помощью DSLdecodeStructure(). Внутри блока её получателем лямбды является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) } -
Используйте аннотацию
@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 сериализатора и индекс кодируемого элемента.
Вот пример пользовательского сериализатора, который кодирует значения 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, если текущую структуру можно декодировать по порядку. Обработайте этот случай отдельно, чтобы избежать более сложной логики декодирования отдельных элементов не по порядку.
Вот пример использования 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.
Чтобы реализовать контекстную сериализацию:
-
Пометьте свойство сериализуемого класса аннотацией
@Contextual. -
Создайте
SerializersModuleс помощью функции построителяSerializersModule(). Используйте функциюcontextual(), чтобы зарегистрировать пользовательский сериализатор для контекстной сериализации.Если
SerializersModuleотсутствует, при сериализации или десериализации типов с контекстными аннотациями, для которых не предусмотрен сериализатор по умолчанию, возникает исключениеSerializationException. Создайте экземпляр
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]) }
}
Дальнейшие шаги
Узнайте, как преобразовывать структуру JSON, изменяя дерево элементов JSON вместо создания пользовательского сериализатора.
Узнайте о альтернативных и пользовательских форматах сериализации, позволяющих реализовать специфичные для формата представления ваших данных.
© 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