Spec-Zone.ru › Kotlin 2

Сериализация полиморфных классов

Полиморфизм позволяет работать с объектами разных типов через общий интерфейс или базовый класс. Сериализация Kotlin поддерживает полиморфизм подтипов, позволяя сериализовать значения разных типов через общий объявленный супертип.

Обзор работы интерфейсов и наследования в Kotlin см. в разделах Интерфейсы и Наследование.

По умолчанию сериализация Kotlin является статической. Чтобы определить, какие свойства кодировать, она использует статический тип сериализуемого значения — объявленный тип переменной.

Это означает, что сериализуются только свойства, определённые в этом статическом типе, даже если во время выполнения значение является экземпляром подкласса.

Вот пример иерархии полиморфных классов:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
@Serializable
open class Project(val name: String)

class OwnedProject(name: String, val owner: String) : Project(name)

fun main() {
    // Uses Project as the declared static type
    val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")

    // Serializes only properties defined in the static type
    println(Json.encodeToString(data))
    // {"name":"kotlinx.coroutines"}
}
//sampleEnd

В этом примере, несмотря на то что во время выполнения значение является подклассом OwnedProject, сериализуются только свойства объявленного статического типа Project.

Для сериализации значений в иерархии полиморфных классов сериализация Kotlin предлагает два подхода:

  • Закрытый полиморфизм, при котором базовый класс или интерфейс sealed гарантирует, что все подклассы известны во время компиляции.

  • Открытый полиморфизм, при котором вы явно указываете подклассы для базового класса open или abstract.

Сериализация закрытых полиморфных классов

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

Чтобы обеспечить эту гарантию, используйте sealed class или sealed interface в качестве базового типа. Пометьте его как @Serializable, а все подклассы — как @Serializable:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
// Defines a sealed class as the base type
@Serializable
sealed class Project {
    abstract val name: String
}

// Marks the subclass as serializable
@Serializable
class OwnedProject(override val name: String, val owner: String) : Project()

// Serializes data using the base type as the static type
fun main() {
    // Uses the base type as the static type
    val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")

    // A type property is added to identify the serialized subclass
    println(Json.encodeToString(data))
    // {"type":"OwnedProject","name":"kotlinx.coroutines","owner":"kotlin"}
}
//sampleEnd

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

Также можно настроить JSON так, чтобы для дискриминатора класса использовалось другое имя ключа. Дополнительную информацию см. в разделе Указание дискриминатора класса для полиморфизма.

Вот пример, в котором подкласс является статическим типом, поэтому свойство type не включается:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
@Serializable
sealed class Project {
    abstract val name: String
}

@Serializable
class OwnedProject(override val name: String, val owner: String) : Project()

fun main() {
    // The inferred static type is OwnedProject
    val data = OwnedProject("kotlinx.coroutines", "kotlin")
    
    // The type property is omitted because the static type is OwnedProject
    println(Json.encodeToString(data))
    // {"name":"kotlinx.coroutines","owner":"kotlin"}
}
//sampleEnd

При сериализации объектов можно явно указать базовый тип, чтобы гарантировать включение свойства type в выходные данные. Для этого передайте базовый тип в качестве аргумента типа функции encodeToString():

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*


@Serializable
sealed class Project {
    abstract val name: String
}

@Serializable
class OwnedProject(override val name: String, val owner: String) : Project()

//sampleStart
fun main() {
    // The inferred static type is OwnedProject
    val data = OwnedProject("kotlinx.coroutines", "kotlin")

    // Specifies the base type explicitly to make use of polymorphism
    println(Json.encodeToString<Project>(data))
    // {"type":"OwnedProject","name":"kotlinx.coroutines","owner":"kotlin"}
}
//sampleEnd

Определение пользовательских сериализуемых имён подклассов

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

Чтобы значение дискриминатора класса оставалось неизменным, задайте пользовательское сериализуемое имя с помощью аннотации @SerialName.

Каждый сериализуемый класс должен иметь уникальное значение @SerialName. Не используйте одно и то же имя для разных классов, даже в отдельных полиморфных иерархиях.

Если в одной иерархии повторно использовать одно и то же имя, функции кодирования или декодирования выбросят IllegalStateException. Повторное использование одного имени в разных иерархиях может привести к труднообнаружимым проблемам сериализации.

Используйте @SerialName, чтобы задать стабильный идентификатор подкласса, не зависящий от исходного кода:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
@Serializable
sealed class Project {
    abstract val name: String
}

// Assigns a custom serial name to the subclass
@Serializable
@SerialName("owned")
class OwnedProject(override val name: String, val owner: String) : Project()

fun main() {
    val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")
    println(Json.encodeToString(data))
    // {"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}
}
//sampleEnd

Свойства базового класса с полями хранения

В иерархии полиморфных классов базовый класс может определять свойства с полями хранения, которые сериализуются вместе со свойствами подклассов. Например:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
@Serializable
sealed class Project {
    abstract val name: String
    var status = "open"
}
            
@Serializable   
@SerialName("owned")
class OwnedProject(override val name: String, val owner: String) : Project()

fun main() {
    // Configures a Json instance to encode default values
    val json = Json { encodeDefaults = true }

    val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")
    // Serializes base class properties together with subclass properties
    println(json.encodeToString(data))
    // {"type":"owned","status":"open","name":"kotlinx.coroutines","owner":"kotlin"}
}
//sampleEnd

Сериализация объектов в иерархиях полиморфных классов

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

Чтобы включить такие объекты в сериализацию, аннотируйте их с помощью @Serializable:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
// Defines a sealed base class
@Serializable
sealed class Response

// Defines an object subclass
@Serializable
object EmptyResponse : Response()

// Defines a class that extends Response
@Serializable   
class TextResponse(val text: String) : Response()

// Serializes a list containing different subclasses
fun main() {
    val list = listOf(EmptyResponse, TextResponse("OK"))
    println(Json.encodeToString(list))
    // [{"type":"EmptyResponse"},{"type":"TextResponse","text":"OK"}]
}
//sampleEnd

Сериализация открытых полиморфных классов

Сериализация Kotlin поддерживает открытый полиморфизм для классов и интерфейсов open и abstract. В этой модели подклассы можно определять в любой части кодовой базы, в том числе в других модулях. Поскольку компилятор не может определить все подклассы во время компиляции, их необходимо указывать явно.

Используйте открытый полиморфизм, если во время компиляции известны не все подклассы. Чтобы зарегистрировать все подклассы во время выполнения, необходимо указать базовый тип и каждый подкласс в классе SerializersModule:

  1. Создайте SerializersModule с помощью функции-конструктора SerializersModule().

  2. Внутри SerializersModule используйте функцию polymorphic(), чтобы указать базовый тип.

  3. Внутри блока polymorphic() зарегистрируйте каждый подкласс с помощью функции subclass(). Регистрация подкласса добавляет его в набор подклассов, связанных с базовым типом, благодаря чему он становится доступен для полиморфной сериализации и десериализации.

  4. Добавьте SerializersModule в экземпляр Json с помощью свойства serializersModule.

  5. При необходимости используйте аннотацию @SerialName, чтобы задать стабильный идентификатор подкласса вместо его полного имени класса.

Чтобы включить свойство type, используйте базовый тип в качестве статического типа. Например:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*

//sampleStart
// Defines a SerializersModule
val module = SerializersModule {

    // Specifies Project as the base type
    polymorphic(Project::class) {

        // Registers OwnedProject as a subclass of Project
        subclass(OwnedProject::class)
    }
}

// Adds the SerializersModule to a Json instance
val format = Json { serializersModule = module }

// Defines the base type used as the static type during serialization
@Serializable
abstract class Project {
    abstract val name: String
}

// Defines a serializable subclass of Project
@Serializable
@SerialName("owned")
class OwnedProject(override val name: String, val owner: String) : Project()

fun main() {
    // Uses Project as the static type to make use of polymorphism
    val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")

    println(format.encodeToString(data))
    // {"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}
}
//sampleEnd

Эта конфигурация создаёт ту же структуру JSON, что и пример в разделе Сериализация закрытых полиморфных классов, но поддерживает классы open и abstract.

Этот пример работает только на JVM из-за ограничений функции serializer(). В Kotlin/JS и Kotlin/Native используйте явный сериализатор: format.encodeToString(PolymorphicSerializer(Project::class), data).

Следить за обсуждением этой проблемы можно на GitHub.

Сериализация интерфейсов в открытых полиморфных иерархиях

Интерфейсы нельзя аннотировать с помощью @Serializable. Сериализация Kotlin рассматривает интерфейсы как полиморфные и по умолчанию использует в качестве их сериализатора PolymorphicSerializer.

Чтобы использовать интерфейс как базовый тип при открытой полиморфной сериализации, пометьте реализующие его классы с помощью @Serializable и зарегистрируйте их в SerializersModule:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*

//sampleStart
// Defines a SerializersModule
val module = SerializersModule {
    polymorphic(Project::class) {
        subclass(OwnedProject::class)
    }
}

val format = Json { serializersModule = module }

// Defines an interface for polymorphic serialization
interface Project {
    val name: String
}

// OwnedProject implements the Project interface 
@Serializable
@SerialName("owned")
class OwnedProject(override val name: String, val owner: String) : Project

fun main() {
    // Uses the interface for serialization
    val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")
    println(format.encodeToString(data))
    // {"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}
}
//sampleEnd

В Kotlin/JS и Kotlin/Native необходимо явно указать сериализатор с помощью format.encodeToString(PolymorphicSerializer(Project::class), data) из-за ограниченных возможностей рефлексии на этих платформах.

Интерфейс также можно использовать в качестве свойства сериализуемого класса. В этом случае сериализация Kotlin также применяет PolymorphicSerializer к этому свойству:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*

val module = SerializersModule {
    polymorphic(Project::class) {
        subclass(OwnedProject::class)
    }
}

val format = Json { serializersModule = module }

//sampleStart
interface Project {
    val name: String
}

@Serializable
@SerialName("owned")
class OwnedProject(override val name: String, val owner: String) : Project

// Defines a serializable class with an interface property
@Serializable
class Data(val project: Project)

fun main() {
    val data = Data(OwnedProject("kotlinx.coroutines", "kotlin"))
    println(format.encodeToString(data))
    // {"project":{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}}
}
//sampleEnd

Выбор базового типа полиморфной иерархии

Сериализация Kotlin полностью статическая. Она определяет базовый тип полиморфной иерархии во время компиляции. Можно выбрать любой базовый тип, включая Any, если явно настроить его.

Для этого:

  1. Укажите базовый тип и его подклассы в SerializersModule.

  2. Если у базового типа нет собственного сериализатора, передайте PolymorphicSerializer для этого типа при вызове функции encodeToString().

Вот пример:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*

//sampleStart
val module = SerializersModule {
    // Registers OwnedProject for the Any base type
    polymorphic(Any::class) {
        subclass(OwnedProject::class)
    }
}

val format = Json { serializersModule = module }

@Serializable
abstract class Project {
    abstract val name: String
}

@Serializable
@SerialName("owned")
class OwnedProject(override val name: String, val owner: String) : Project()

fun main() {
    // Uses Any as the static type
    val data: Any = OwnedProject("kotlinx.coroutines", "kotlin")

    // Specifies a PolymorphicSerializer for the Any base type
    println(format.encodeToString(PolymorphicSerializer(Any::class), data))
    // {"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}
}
//sampleEnd

Если не указать PolymorphicSerializer, будет выброшено следующее исключение:

Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found.
Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied.

Один и тот же подкласс можно использовать и с несколькими базовыми типами, если явно зарегистрировать его для каждого базового типа.

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

Вот пример, в котором в качестве базовых типов используются и Project, и BaseProject:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*

//sampleStart
val module = SerializersModule {
    // Defines a helper function that registers subclasses
    fun PolymorphicModuleBuilder<Project>.registerProjectSubclasses() {
        subclass(OwnedProject::class)
    }

    // Registers the same subclass for each base type Project and  BaseProject
    polymorphic(BaseProject::class) { registerProjectSubclasses() }
    polymorphic(Project::class) { registerProjectSubclasses() }
}
//sampleEnd

val format = Json { serializersModule = module }

interface BaseProject {
    val name: String
}

interface Project : BaseProject

@Serializable
@SerialName("owned")
class OwnedProject(override val name: String, val owner: String) : Project

@Serializable
class Data(
    val project: Project,
    val baseProject: BaseProject
)

fun main() {
    val project = OwnedProject("kotlinx.coroutines", "kotlin")
    val data = Data(project, project)
    println(format.encodeToString(data))
    // {"project":{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"},"baseProject":{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}}
}

Полиморфная сериализация свойств

Для интерфейсов и абстрактных классов по умолчанию используется полиморфная сериализация.

Чтобы сериализовать свойство с несериализуемым типом, например Any, или если тип свойства — класс open, аннотируйте свойство с помощью @Polymorphic.

Эта аннотация применяет PolymorphicSerializer к свойству.

Вот пример:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*

val module = SerializersModule {
    polymorphic(Any::class) {
        subclass(OwnedProject::class)
    }
}

val format = Json { serializersModule = module }

interface Project {
    val name: String
}

@Serializable
@SerialName("owned")
class OwnedProject(override val name: String, val owner: String) : Project

//sampleStart
@Serializable
class Data(
    // Applies PolymorphicSerializer to the property
    @Polymorphic
    val project: Any 
)

fun main() {
    val data = Data(OwnedProject("kotlinx.coroutines", "kotlin"))
    println(format.encodeToString(data))
    // {"project":{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}}
}
//sampleEnd

Полиморфная сериализация открытых классов

Чтобы полиморфно сериализовать класс open, аннотируйте его одновременно с помощью @Serializable и @Polymorphic:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*

val module = SerializersModule {
    polymorphic(Project::class) {
        subclass(OwnedProject::class)
    }
}

val format = Json { serializersModule = module }

//sampleStart
// Applies PolymorphicSerializer to the open class
@Serializable
@Polymorphic
open class Project

@Serializable
@SerialName("owned")
class OwnedProject(val name: String, val owner: String) : Project()

@Serializable
class Data(val project: Project)

fun main() {
    val project = OwnedProject("kotlinx.coroutines", "kotlin")

    val data = Data(project)
    println(format.encodeToString(data))
    // {"project":{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}}
}
//sampleEnd

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

В одной иерархии можно сочетать открытый и закрытый полиморфизм, например, когда корнем иерархии является interface, а различные подиерархии представлены закрытыми классами. Вместо регистрации каждого закрытого подкласса по отдельности можно зарегистрировать все реализации закрытого класса или интерфейса с помощью функции subclassesOfSealed().

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

Вот пример:

@file:OptIn(ExperimentalSerializationApi::class)

// Imports declarations from the serialization library
import kotlinx.serialization.* 
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*

interface Base

@Serializable
sealed interface Sub: Base

@Serializable
class Sub1(val data: String): Sub

val module1 = SerializersModule {
    polymorphic(Base::class) {
        subclassesOfSealed(Sub.serializer())
    }
}

val format1 = Json { serializersModule = module1 }

val module2 = SerializersModule {
    polymorphic(Base::class) {
        // Uses the reified version of subclassesOfSealed() to specify the same sealed type
        subclassesOfSealed<Sub>()
    }
}

val format2 = Json { serializersModule = module2 }


fun main() {
    val data: Base = Sub1("kotlin")
    println(format1.encodeToString(data))
    // {"type":"Sub1","data":"kotlin"}

    println(format2.encodeToString(data))
    // {"type":"Sub1","data":"kotlin"}
}

Сериализация обобщённых подтипов в полиморфной иерархии

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

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

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

Вот пример:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*

//sampleStart
@Serializable
abstract class Response<out T>

// Defines a generic polymorphic subtype
@Serializable
@SerialName("OkResponse")
data class OkResponse<out T>(val data: T) : Response<T>()

@Serializable
abstract class Project {
    abstract val name: String
}

// Defines a subtype used as a generic value
@Serializable
@SerialName("OwnedProject")
data class OwnedProject(override val name: String, val owner: String) : Project()

// Defines serializers for a polymorphic hierarchy with generic subtypes
val responseModule = SerializersModule {
    polymorphic(Response::class) {
        // Registers the generic subtype
        // with a serializer that specifies a PolymorphicSerializer
        subclass(OkResponse.serializer(PolymorphicSerializer(Any::class)))
    }
    polymorphic(Any::class) {
        // Registers the subtype used as the generic value
        subclass(OwnedProject::class)
    }
    polymorphic(Project::class) {
        // Registers the same subtype for the static base type
        subclass(OwnedProject::class)
    }
}

// Creates a Json instance with the registered serializers
val format = Json { serializersModule = responseModule }

fun main() {
    // Uses a generic polymorphic type with a concrete subtype
    val data: Response<Project> = OkResponse(OwnedProject("kotlinx.serialization", "kotlin"))

    val jsonString = format.encodeToString(data)
    println(jsonString)
    // {"type":"OkResponse","data":{"type":"OwnedProject","name":"kotlinx.serialization","owner":"kotlin"}}
   
    val deserializedData = format.decodeFromString<Response<Project>>(jsonString)
    println(deserializedData)
    // OkResponse(data=OwnedProject(name=kotlinx.serialization, owner=kotlin))
}
//sampleEnd

В этом примере PolymorphicSerializer(Any::class) позволяет сериализовать обобщённый подтип OkResponse с любым значением, зарегистрированным полиморфно как подтип Any.

Объединение нескольких экземпляров SerializersModule

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

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

Вот пример:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*

@Serializable
abstract class Response<out T>

@Serializable
data class OkResponse<out T>(val data: T) : Response<T>()

val responseModule = SerializersModule {
    polymorphic(Response::class) {
        subclass(OkResponse.serializer(PolymorphicSerializer(Any::class)))
    }
}

@Serializable
abstract class Project {
   abstract val name: String
}

@Serializable
data class OwnedProject(override val name: String, val owner: String) : Project()


val projectModule = SerializersModule {
    fun PolymorphicModuleBuilder<Project>.registerProjectSubclasses() {
        subclass(OwnedProject::class)
    }
    polymorphic(Any::class) { registerProjectSubclasses() }
    polymorphic(Project::class) { registerProjectSubclasses() }
}
//sampleStart
// Merges the SerializersModule instances from both hierarchies
val format = Json { serializersModule = projectModule + responseModule }
//sampleEnd

fun main() {
    val data: Response<Project> = OkResponse(OwnedProject("kotlinx.serialization", "kotlin"))
    val string = format.encodeToString(data)
    println(string)
    // {"type":"OkResponse","data":{"type":"OwnedProject","name":"kotlinx.serialization","owner":"kotlin"}}

    println(format.decodeFromString<Response<Project>>(string))
    // OkResponse(data=OwnedProject(name=kotlinx.serialization, owner=kotlin))
}

Чтобы объединить модули внутри блока SerializersModule, используйте функцию include():

// Merges multiple SerializersModule instances using include()
val combinedModule = SerializersModule {
    include(projectModule)
    include(responseModule)
}

Если вы предоставите SerializersModule своей библиотеки или общего модуля, пользователи смогут объединить его со своим SerializersModule.

Десериализация неизвестных полиморфных подтипов с помощью сериализатора по умолчанию

При десериализации полиморфных данных сериализация Kotlin определяет подтип по свойству type. Если подтип не зарегистрирован, десериализация завершается ошибкой с SerializationException.

Можно определить сериализатор по умолчанию для обработки незарегистрированных или неизвестных полиморфных подтипов.

Рассмотрим следующий пример, где Project — базовый тип, OwnedProject — зарегистрированный подтип, а BasicProject обозначает неизвестные подтипы проекта:

@Serializable
abstract class Project {
    abstract val name: String
}

// Represents unknown project types
@Serializable
data class BasicProject(override val name: String, val type: String): Project()

@Serializable
@SerialName("OwnedProject")
data class OwnedProject(override val name: String, val owner: String) : Project()

Чтобы обрабатывать неизвестные полиморфные подтипы Project, настройте для базового типа сериализатор по умолчанию. Используйте функцию defaultDeserializer() внутри блока polymorphic(), чтобы задать резервный вариант для таких незарегистрированных подтипов:

val module = SerializersModule {
    polymorphic(Project::class) {
        subclass(OwnedProject::class)
        defaultDeserializer { BasicProject.serializer() }
    }
}

С помощью этой конфигурации SerializersModule можно десериализовать как зарегистрированные, так и незарегистрированные подтипы:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*

@Serializable
abstract class Project {
    abstract val name: String
}

// Represents unknown project types
@Serializable
data class BasicProject(override val name: String, val type: String): Project()

@Serializable
@SerialName("OwnedProject")
data class OwnedProject(override val name: String, val owner: String) : Project()

// Registers a default deserializer for unknown Project subtypes
val module = SerializersModule {
    polymorphic(Project::class) {
        subclass(OwnedProject::class)
        defaultDeserializer { BasicProject.serializer() }
    }
}

//sampleStart
val format = Json { serializersModule = module }

fun main() {
    // Deserializes both a known and an unknown Project subtype
    println(format.decodeFromString<List<Project>>("""
        [
            {"type":"unknown","name":"example"},
            {"type":"OwnedProject","name":"kotlinx.serialization","owner":"kotlin"} 
        ]
    """))
    // [BasicProject(name=example, type=unknown), OwnedProject(name=kotlinx.serialization, owner=kotlin)]
}
//sampleEnd

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

Для JSON также можно настроить формат так, чтобы он игнорировал неизвестные ключи, если неизвестные подтипы содержат дополнительные свойства, но при этом соответствуют структуре, ожидаемой сериализатором по умолчанию. Если структура неизвестных данных различается, используйте вместо этого пользовательский сериализатор.

Дополнительные сведения о более гибкой обработке входных данных JSON при десериализации см. в разделе Изменение структуры JSON.

Сериализация полиморфных типов с помощью сериализатора по умолчанию

Можно сериализовать значения полиморфного базового типа, не регистрируя каждый конкретный подтип. Используйте этот способ, если у вас нет доступа ко всей иерархии типов или если она часто меняется.

Для этого:

  1. Используйте функцию polymorphicDefaultSerializer() в блоке SerializersModule.

  2. Укажите лямбда-выражение в polymorphicDefaultSerializer(), возвращающее SerializationStrategy для значения во время выполнения.

Рассмотрим пример с двумя закрытыми классами: CatImpl и DogImpl. Чтобы не делать их видимыми, зарегистрируйте сериализатор по умолчанию для Animal, который выбирает сериализатор на основе типа во время выполнения через публичные интерфейсы:

interface Animal

interface Cat : Animal {
    val catType: String
}

interface Dog : Animal {
    val dogType: String
}

private class CatImpl : Cat {
    override val catType: String = "Tabby"
}

private class DogImpl : Dog {
    override val dogType: String = "Husky"
}

object AnimalProvider {
    fun createCat(): Cat = CatImpl()
    fun createDog(): Dog = DogImpl()
}

// Registers a default serializer for unknown Animal subtypes
val module = SerializersModule {
    polymorphicDefaultSerializer(Animal::class) { instance ->
        @Suppress("UNCHECKED_CAST")
        // Determines the appropriate serializer using a when block
        when (instance) {
            is Cat -> CatSerializer as SerializationStrategy<Animal>
            is Dog -> DogSerializer as SerializationStrategy<Animal>
            else -> null
        }
    }
}

Определите сериализаторы для Cat и Dog, а затем создайте экземпляр Json, который использует SerializersModule со свойством serializersModule, чтобы включить полиморфную сериализацию значений Animal:

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.modules.*

interface Animal

interface Cat : Animal {
    val catType: String
}

interface Dog : Animal {
    val dogType: String
}

private class CatImpl : Cat {
    override val catType: String = "Tabby"
}

private class DogImpl : Dog {
    override val dogType: String = "Husky"
}

object AnimalProvider {
    fun createCat(): Cat = CatImpl()
    fun createDog(): Dog = DogImpl()
}

// Registers a default serializer for unknown Animal subtypes
val module = SerializersModule {
    polymorphicDefaultSerializer(Animal::class) { instance ->
        @Suppress("UNCHECKED_CAST")
        // Determines the appropriate serializer using a when block based on the runtime value
        when (instance) {
            is Cat -> CatSerializer as SerializationStrategy<Animal>
            is Dog -> DogSerializer as SerializationStrategy<Animal>
            else -> null
        }
    }
}

//sampleStart
// Defines custom serializers
object CatSerializer : SerializationStrategy<Cat> {
    override val descriptor = buildClassSerialDescriptor("Cat") {
        element<String>("catType")
    }

    override fun serialize(encoder: Encoder, value: Cat) {
        encoder.encodeStructure(descriptor) {
            encodeStringElement(descriptor, 0, value.catType)
        }
    }
}

object DogSerializer : SerializationStrategy<Dog> {
    override val descriptor = buildClassSerialDescriptor("Dog") {
        element<String>("dogType")
    }

    override fun serialize(encoder: Encoder, value: Dog) {
        encoder.encodeStructure(descriptor) {
            encodeStringElement(descriptor, 0, value.dogType)
        }
    }
}

val format = Json { serializersModule = module }

fun main() {
    // Serializes an instance of Cat
    println(format.encodeToString<Animal>(AnimalProvider.createCat()))
    // {"type":"Cat","catType":"Tabby"}
}
//sampleEnd

Что дальше

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

9 июня 2026 г.
Сериализация 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-polymorphism.html

Spec-Zone.ru

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