跳转至

多态性

This is the fourth chapter of the Kotlin Serialization Guide. In this chapter we’ll see how Kotlin Serialization deals with polymorphic class hierarchies.
这是《Kotlin 序列化指南》的第四章。在本章中,我们将了解 Kotlin 序列化如何处理多态类层次结构。

闭合多态性

Closed polymorphism

Let us start with basic introduction to polymorphism.
让我们从多态性的基本介绍开始。

静态类型

Static types

Kotlin Serialization is fully static with respect to types by default. The structure of encoded objects is determined by compile-time types of objects. Let’s examine this aspect in more detail and learn how to serialize polymorphic data structures, where the type of data is determined at runtime.

默认情况下,Kotlin 序列化对于类型是完全静态的。编码对象的结构由对象的编译时类型确定。让我们更详细地研究这一方面,并学习如何序列化多态数据结构,其中数据类型是在运行时确定的。

To show the static nature of Kotlin Serialization let us make the following setup. An open class Project has just the name property, while its derived class OwnedProject adds an owner property. In the below example, we serialize data variable with a static type of Project that is initialized with an instance of OwnedProject at runtime.

为了显示 Kotlin 序列化的静态特性,让我们进行以下设置。一个 open class Project 类,只具有 name 属性,而子类 OwnedProject 添加了属性 owner 。在下面的示例中,我们将 data 变量序列化为静态类型的 Project,该类型在运行时被初始化为 OwnedProject的实例。

@Serializable
open class Project(val name: String)

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

fun main() {
    val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")
    println(Json.encodeToString(data))
}  

You can get the full code here.
您可以在此处获取完整代码。

Despite the runtime type of OwnedProject, only the Project class properties are getting serialized.
尽管运行时类型为 OwnedProject ,但只有 Project 类属性被序列化。

{"name":"kotlinx.coroutines"}

Let’s change the compile-time type of data to OwnedProject.
让我们将编译 data 时类型更改为 OwnedProject 。

@Serializable
open class Project(val name: String)

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

fun main() {
    val data = OwnedProject("kotlinx.coroutines", "kotlin")
    println(Json.encodeToString(data))
}  

You can get the full code here.
您可以在此处获取完整代码。

We get an error, because the OwnedProject class is not serializable.
我们收到一个错误,因为该 OwnedProject 类不可序列化。

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

设计可序列化的层次结构

Designing serializable hierarchy

We cannot simply mark OwnedProject from the previous example as @Serializable. It does not compile, running into the constructor properties requirement. To make hierarchy of classes serializable, the properties in the parent class have to be marked abstract, making the Project class abstract, too.

我们不能简单地将上一示例中的 OwnedProject 标记为 @Serializable。它无法编译,会遇到 [构造函数属性要求]。要使类的层次结构可序列化,父类中的属性必须标记为abstract,从而使Project类也成为抽象类。

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

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

fun main() {
    val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")
    println(Json.encodeToString(data))
}  

You can get the full code here.
您可以在此处获取完整代码。

This is close to the best design for a serializable hierarchy of classes, but running it produces the following error:
这接近于可序列化的类层次结构的最佳设计,但运行它会产生以下错误:

Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for subclass 'OwnedProject' is not found in the polymorphic scope of 'Project'.
Check if class with serial name 'OwnedProject' exists and serializer is registered in a corresponding SerializersModule.
To be registered automatically, class 'OwnedProject' has to be '@Serializable', and the base class 'Project' has to be sealed and '@Serializable'.

密封类

Sealed classes

The most straightforward way to use serialization with a polymorphic hierarchy is to mark the base class sealedAll subclasses of a sealed class must be explicitly marked as @Serializable.

将序列化与多态层次结构一起使用的最直接方法是标记基类为密封类 sealed 。密封类的所有子类都必须显式标记为 @Serializable 。

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

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

fun main() {
    val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")
    println(Json.encodeToString(data)) // Serializing data of compile-time type Project
}  

You can get the full code here.
您可以在此处获取完整代码。

Now we can see a default way to represent polymorphism in JSON. A type key is added to the resulting JSON object as a discriminator.

现在,我们可以看到在JSON中表示多态性的默认方法。在生成的 JSON 对象中添加了一个type用于区分类型。

{"type":"example.examplePoly04.OwnedProject","name":"kotlinx.coroutines","owner":"kotlin"}

Pay attention to the small, but very important detail in the above example that is related to Static types: the val data property has a compile-time type of Project, even though its run-time type is OwnedProject. When serializing polymorphic class hierarchies you must ensure that the compile-time type of the serialized object is a polymorphic one, not a concrete one.

请注意上例中与 [Static types] 有关的一个很小但非常重要的细节:val data 属性的编译时类型是Project,尽管其运行时类型是OwnedProject。在序列化多态类层次结构时,必须确保序列化对象的编译时类型是多态类型,而不是具体类型。

Let us see what happens if the example is slightly changed, so that the compile-time of the object that is being serialized is OwnedProject (the same as its run-time type).
让我们看看如果对示例稍作修改,使被序列化对象的编译时类型为 OwnedProject(与其运行时类型相同),会发生什么情况。

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

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

fun main() {
    val data = OwnedProject("kotlinx.coroutines", "kotlin") // data: OwnedProject here
    println(Json.encodeToString(data)) // Serializing data of compile-time type OwnedProject
}  

You can get the full code here.
您可以在此处获取完整代码。

The type of OwnedProject is concrete and is not polymorphic, thus the type discriminator property is not emitted into the resulting JSON.
的 OwnedProject 类型是具体的,不是多态的,因此 type属性不会添加到生成的 JSON 中。

{"name":"kotlinx.coroutines","owner":"kotlin"}

In general, Kotlin Serialization is designed to work correctly only when the compile-time type used during serialization is the same one as the compile-time type used during deserialization. You can always specify the type explicitly when calling serialization functions. The previous example can be corrected to use Project type for serialization by calling Json.encodeToString<Project>(data).
一般来说,只有当序列化过程中使用的编译时类型与反序列化过程中使用的编译时类型相同时,Kotlin 序列化才能正常工作。您可以在调用序列化函数时明确指定类型。可以通过调用 Json.encodeToString<Project>(data),将上例更正为使用 Project 类型进行序列化。

自定义子类序列化名称

Custom subclass serial name

A value of the type key is a fully qualified class name by default. We can put SerialName annotation onto the corresponding class to change it.

默认情况下, type 键的值是完全限定的类名。我们可以将 SerialName 注解放在相应的类上以更改它。

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

@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))
}  

You can get the full code here.
您可以在此处获取完整代码。

This way we can have a stable serial name that is not affected by the class’s name in the source code.
这样,我们就可以拥有一个稳定的序列化名称,不受源代码中类名的影响。

{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}

In addition to that, JSON can be configured to use a different key name for the class discriminator. You can find an example in the Class discriminator for polymorphism section.
除此之外,还可以将 JSON 配置为对类鉴别器使用不同的键名。您可以在多态性的类鉴别器部分找到一个示例。

基类中的具体属性

Concrete properties in a base class

A base class in a sealed hierarchy can have properties with backing fields.
密封层次结构中的基类可以有带支持字段的属性(有getter和setter)。

@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() {
    val json = Json { encodeDefaults = true } // "status" will be skipped otherwise
    val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")
    println(json.encodeToString(data))
}  

You can get the full code here.
您可以在此处获取完整代码。

The properties of the superclass are serialized before the properties of the subclass.
超类的属性在子类的属性之前序列化。

{"type":"owned","status":"open","name":"kotlinx.coroutines","owner":"kotlin"}

object类

Objects 

Sealed hierarchies can have objects as their subclasses and they also need to be marked as @Serializable. Let’s take a different example with a hierarchy of Response classes.

密封层次结构可以将object类作为其子类,并且还需要将它们标记为 @Serializable 。让我们用一个不同的例子来说明 Response 类的层次结构。

@Serializable
sealed class Response

@Serializable
object EmptyResponse : Response()

@Serializable   
class TextResponse(val text: String) : Response()   

Let us serialize a list of different responses.
让我们序列化一个不同response类的list。

fun main() {
    val list = listOf(EmptyResponse, TextResponse("OK"))
    println(Json.encodeToString(list))
}  

You can get the full code here.
您可以在此处获取完整代码。

An object serializes as an empty class, also using its fully-qualified class name as type by default:
对象序列化为空类,默认情况下也使用其完全限定的类名作为类型:

[{"type":"example.examplePoly08.EmptyResponse"},{"type":"example.examplePoly08.TextResponse","text":"OK"}]

Even if object has properties, they are not serialized.
即使对象具有属性,它们也不会序列化。

开放多态性

Open polymorphism

Serialization can work with arbitrary open classes or abstract classes. However, since this kind of polymorphism is open, there is a possibility that subclasses are defined anywhere in the source code, even in other modules, the list of subclasses that are serialized cannot be determined at compile-time and must be explicitly registered at runtime.

序列化可以处理任意的 open classabstract class。但是,由于这种多态性是开放的,因此有可能在源代码的任何地方,甚至在其他模块中定义了子类,因此序列化的子类列表无法在编译时确定,必须在运行时显式注册。

已注册的子类

Registered subclasses

Let us start with the code from the Designing serializable hierarchy section. To make it work with serialization without making it sealed, we have to define a SerializersModule using the SerializersModule {} builder function. In the module the base class is specified in the polymorphic builder and each subclass is registered with the subclass function. Now, a custom JSON configuration can be instantiated with this module and used for serialization.

让我们从设计可序列化层次结构部分的代码开始。为了在不密封的情况下实现序列化,我们必须使用序列化模块(SerializersModule {} builder)函数定义一个序列化模块(SerializersModule)。在该模块中,基类是在多态生成器中指定的,每个子类都是通过子类函数注册的。现在,自定义 JSON 配置可以通过该模块实例化并用于序列化。

Details on custom JSON configurations can be found in the JSON configuration section.
有关自定义 JSON 配置的详细信息,请参阅 JSON 配置部分。

val module = SerializersModule {
    polymorphic(Project::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() {
    val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")
    println(format.encodeToString(data))
}    

You can get the full code here.
您可以在此处获取完整代码。

This additional configuration makes our code work just as it worked with a sealed class in the Sealed classes section, but here subclasses can be spread arbitrarily throughout the code.

这种额外的配置使我们的代码就像在“密封类”部分中使用密封类一样工作,但在这里,子类可以任意分布在任意位置。

{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}

Please note that this example works only on JVM because of serializer function restrictions. For JS and Native, explicit serializer should be used: format.encodeToString(PolymorphicSerializer(Project::class), data) You can keep track of this issue here.
请注意,由于 serializer 功能限制,此示例仅适用于 JVM。对于 JS 和 Native,应使用显式序列化程序: format.encodeToString(PolymorphicSerializer(Project::class), data) 您可以在此处跟踪此问题。

序列化接口类

Serializing interfaces

We can update the previous example and turn Project superclass into an interface. However, we cannot mark an interface itself as @Serializable. No problem. Interfaces cannot have instances by themselves. Interfaces can only be represented by instances of their derived classes. Interfaces are used in the Kotlin language to enable polymorphism, so all interfaces are considered to be implicitly serializable with the PolymorphicSerializer strategy. We just need to mark their implementing classes as @Serializable and register them.

我们可以更新前面的示例,将 Project 超类变成一个接口。但是,我们不能将接口本身标记为 @Serializable。没问题。接口本身不能拥有实例。接口只能由其派生类的实例来表示。Kotlin 语言中使用接口来实现多态性,因此使用 [PolymorphicSerializer] 策略,所有接口都被认为是隐式可序列化的。我们只需将它们的实现类标记为 @Serializable 并注册即可。

interface Project {
    val name: String
}

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

Now if we declare data with the type of Project we can simply call format.encodeToString as before.
现在,如果我们以 Project 类型声明 data ,就可以像以前一样简单地调用 format.encodeToString

fun main() {
    val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")
    println(format.encodeToString(data))
}    

You can get the full code here.
您可以在此处获取完整代码。

{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}

Note: On Kotlin/Native, you should use format.encodeToString(PolymorphicSerializer(Project::class), data)) instead due to limited reflection capabilities.
注意:在 Kotlin/Native 上,由于反射功能有限,您应该改用 format.encodeToString(PolymorphicSerializer(Project::class), data)) 。

接口类型的属性

Property of an interface type

Continuing the previous example, let us see what happens if we use Project interface as a property in some other serializable class. Interfaces are implicitly polymorphic, so we can just declare a property of an interface type.

继续前面的例子,让我们看看如果把 Project 接口作为其他可序列化类的属性会发生什么。接口是隐式多态的,因此我们可以直接声明一个接口类型的属性。

@Serializable
class Data(val project: Project) // Project is an interface

fun main() {
    val data = Data(OwnedProject("kotlinx.coroutines", "kotlin"))
    println(format.encodeToString(data))
}        

You can get the full code here.
您可以在此处获取完整代码。

As long as we’ve registered the actual subtype of the interface that is being serialized in the SerializersModule of our format, we get it working at runtime.

只要我们在 format SerializersModule 中注册了正在序列化的接口的实际子类型,我们就可以让它在运行时工作。

{"project":{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}}

多态性的静态父类型查找

Static parent type lookup for polymorphism

During serialization of a polymorphic class the root type of the polymorphic hierarchy (Project in our example) is determined statically. Let us take the example with the serializable abstract class Project, but change the main function to declare data as having a type of Any:

在多态类的序列化过程中,多态层次结构的根类型(在我们的示例中为 Project)是静态确定的。让我们以可序列化的 “抽象类 Project “为例,改变 “main “函数,将 “data “声明为 “Any “类型:

fun main() {
    val data: Any = OwnedProject("kotlinx.coroutines", "kotlin")
    println(format.encodeToString(data))
}    

You can get the full code here.
您可以在此处获取完整代码。

We get the exception.
我们得到了异常。

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.

We have to register classes for polymorphic serialization with respect for the corresponding static type we use in the source code. First of all, we change our module to register a subclass of Any:

我们必须针对源代码中使用的相应静态类型注册多态序列化类。首先,我们更改模块,注册一个 Any 的子类:

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

Then we can try to serialize the variable of type Any:
然后,我们就可以尝试序列化 Any 类型的变量了:

fun main() {
    val data: Any = OwnedProject("kotlinx.coroutines", "kotlin")
    println(format.encodeToString(data))
}    

You can get the full code here.
您可以在此处获取完整代码。

However, Any is a class and it is not serializable:
然而,虽然Any是一个类, 但它不可序列化:

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.

We must to explicitly pass an instance of PolymorphicSerializer for the base class Any as the first parameter to the encodeToString function.

我们必须将基类 Any 的 PolymorphicSerializer 实例显式传递给 encodeToString 函数的第一个参数。

fun main() {
    val data: Any = OwnedProject("kotlinx.coroutines", "kotlin")
    println(format.encodeToString(PolymorphicSerializer(Any::class), data))
}    

You can get the full code here.
您可以在此处获取完整代码。

With the explicit serializer it works as before.
使用显式序列化程序,它的工作方式与以前一样。

{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}

显式标记多态类属性

Explicitly marking polymorphic class properties

The property of an interface type is implicitly considered polymorphic, since interfaces are all about runtime polymorphism. However, Kotlin Serialization does not compile a serializable class with a property of a non-serializable class type. If we have a property of Any class or other non-serializable class, then we must explicitly provide its serialization strategy via the @Serializable annotation as we saw in the Specifying serializer on a property section. To specify a polymorphic serialization strategy of a property, the special-purpose @Polymorphic annotation is used.

接口类型的属性被隐含地认为是多态的,因为接口就是关于运行时多态性的。但是,Kotlin 序列化不会编译带有不可序列化类类型属性的可序列化类。如果我们有一个 Any 类或其他不可序列化类的属性,那么我们必须通过 @Serializable 注解显式地提供其序列化策略,正如我们在 “在属性上指定序列化器 “一节中所看到的那样。要指定一个属性的多态序列化策略,需要使用特殊用途的 @Polymorphic 注解。

@Serializable
class Data(
    @Polymorphic // the code does not compile without it 
    val project: Any 
)

fun main() {
    val data = Data(OwnedProject("kotlinx.coroutines", "kotlin"))
    println(format.encodeToString(data))
}

You can get the full code here.
您可以在此处获取完整代码。

注册多个超类

Registering multiple superclasses

When the same class gets serialized as a value of properties with different compile-time type from the list of its superclasses, we must register it in the SerializersModule for each of its superclasses separately. It is convenient to extract registration of all the subclasses into a separate function and use it for each superclass. You can use the following template to write it.

当同一个类被序列化为其超类列表中编译时类型不同的属性值时,我们必须在 [SerializersModule] 中为每个超类分别注册。将所有子类的注册提取到一个单独的函数中并用于每个超类是很方便的。你可以使用下面的模板来编写它。

val module = SerializersModule {
    fun PolymorphicModuleBuilder<Project>.registerProjectSubclasses() {
        subclass(OwnedProject::class)
    }
    polymorphic(Any::class) { registerProjectSubclasses() }
    polymorphic(Project::class) { registerProjectSubclasses() }
}        

You can get the full code here.
您可以在此处获取完整代码。

多态性和泛型类

Polymorphism and generic classes

Generic subtypes for a serializable class require a special handling. Consider the following hierarchy.
可序列化类的泛型子类型需要特殊处理。请考虑以下层次结构。

@Serializable
abstract class Response<out T>

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

Kotlin Serialization does not have a builtin strategy to represent the actually provided argument type for the type parameter T when serializing a property of the polymorphic type OkResponse<T>. We have to provide this strategy explicitly when defining the serializers module for the Response. In the below example we use OkResponse.serializer(...) to retrieve the Plugin-generated generic serializer of the OkResponse class and instantiate it with the PolymorphicSerializer instance with Any class as its base. This way, we can serialize an instance of OkResponse with any data property that was polymorphically registered as a subtype of Any.

在序列化多态类型OkResponse<T>的属性时,Kotlin 序列化没有内置策略来表示类型参数 T 实际提供的参数类型。我们必须在为 Response 定义序列化模块时明确提供这一策略。在下面的示例中,我们使用 OkResponse.serializer(...)来获取由插件生成的 OkResponse 类的通用序列化器,并将其与以 Any 类为基类的 PolymorphicSerializer 实例进行实例化。这样,我们就可以序列化 OkResponse 实例的任何数据属性,这些数据属性已被多态注册为 Any 的子类型。

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

合并库序列化程序模块

Merging library serializers modules

When the application grows in size and splits into source code modules, it may become inconvenient to store all class hierarchies in one serializers module. Let us add a library with the Project hierarchy to the code from the previous section.

当应用程序规模扩大并分割成多个源代码模块时,将所有类的层次结构存储在一个序列化模块中可能会变得不方便。让我们在上一节的代码中添加一个具有 Project 层次结构的库。

val projectModule = SerializersModule {
    fun PolymorphicModuleBuilder<Project>.registerProjectSubclasses() {
        subclass(OwnedProject::class)
    }
    polymorphic(Any::class) { registerProjectSubclasses() }
    polymorphic(Project::class) { registerProjectSubclasses() }
}

We can compose those two modules together using the plus operator to merge them, so that we can use them both in the same Json format instance.

我们可以使用 plus 运算符将这两个模块组合在一起以合并它们,以便我们可以在同一个 Json 格式实例中使用它们。

You can also use the include function in the SerializersModule {} DSL.
还可以在 SerializersModule {} DSL 中使用 include 函数。

val format = Json { serializersModule = projectModule + responseModule }

Now classes from both hierarchies can be serialized together and deserialized together.
现在,两个层次结构中的类可以一起序列化和反序列化。

fun main() {
    // both Response and Project are abstract and their concrete subtypes are being serialized
    val data: Response<Project> =  OkResponse(OwnedProject("kotlinx.serialization", "kotlin"))
    val string = format.encodeToString(data)
    println(string)
    println(format.decodeFromString<Response<Project>>(string))
}

You can get the full code here.
您可以在此处获取完整代码。

The JSON that is being produced is deeply polymorphic.
正在生成的 JSON 是深度多态的。

{"type":"OkResponse","data":{"type":"OwnedProject","name":"kotlinx.serialization","owner":"kotlin"}}
OkResponse(data=OwnedProject(name=kotlinx.serialization, owner=kotlin))

If you’re writing a library or shared module with an abstract class and some implementations of it, you can expose your own serializers module for your clients to use so that a client can combine your module with their modules.

如果要编写包含抽象类及其某些实现的库或共享模块,则可以公开自己的序列化程序模块供客户端使用,以便客户端可以将模块与其模块组合在一起。

用于反序列化的默认多态类型处理程序

Default polymorphic type handler for deserialization

What happens when we deserialize a subclass that was not registered?
当我们反序列化未注册的子类时会发生什么?

fun main() {
    println(format.decodeFromString<Project>("""
        {"type":"unknown","name":"example"}
    """))
}

You can get the full code here.
您可以在此处获取完整代码。

We get the following exception.
我们得到以下异常。

Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 0: Serializer for subclass 'unknown' is not found in the polymorphic scope of 'Project' at path: $
Check if class with serial name 'unknown' exists and serializer is registered in a corresponding SerializersModule.

When reading a flexible input we might want to provide some default behavior in this case. For example, we can have a BasicProject subtype to represent all kinds of unknown Project subtypes.

在这种情况下,在读取灵活输入时,我们可能希望提供一些默认行为。例如,我们可以有一个 BasicProject 子类型来表示各种未知 Project 的子类型。

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

@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()

We register a default deserializer handler using the defaultDeserializer function in the polymorphic { ... } DSL that defines a strategy which maps the type string from the input to the deserialization strategy. In the below example we don’t use the type, but always return the Plugin-generated serializer of the BasicProject class.

我们使用 polymorphic { ... } DSL 中的 defaultDeserializer 函数注册一个默认的反序列化程序处理程序,该函数定义了一个策略,该策略将 type 字符串从输入映射到反序列化策略。在下面的示例中,我们没有使用类型,而是始终返回由插件生成的 BasicProject 类的序列化器。

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

Using this module we can now deserialize both instances of the registered OwnedProject and any unregistered one.

使用此模块,我们现在可以反序列化已注册 OwnedProject 和任何未注册的实例。

val format = Json { serializersModule = module }

fun main() {
    println(format.decodeFromString<List<Project>>("""
        [
            {"type":"unknown","name":"example"},
            {"type":"OwnedProject","name":"kotlinx.serialization","owner":"kotlin"} 
        ]
    """))
}

You can get the full code here.
您可以在此处获取完整代码。

Notice, how BasicProject had also captured the specified type key in its type property.
请注意,如何 BasicProject 在其 type 属性中捕获指定的类型键。

[BasicProject(name=example, type=unknown), OwnedProject(name=kotlinx.serialization, owner=kotlin)]

We used a plugin-generated serializer as a default serializer, implying that the structure of the “unknown” data is known in advance. In a real-world API it’s rarely the case. For that purpose a custom, less-structured serializer is needed. You will see the example of such serializer in the future section on Maintaining custom JSON attributes.

我们使用插件生成的序列化器作为默认序列化器,这意味着 “未知 “数据的结构是事先已知的。在现实世界的应用程序接口中,这种情况很少发生。为此,我们需要一个自定义的、结构不那么复杂的序列化器。您将在以后的维护自定义 JSON 属性一节中看到这种序列化器的示例。

用于序列化的默认多态类型处理程序

Default polymorphic type handler for serialization

Sometimes you need to dynamically choose which serializer to use for a polymorphic type based on the instance, for example if you don’t have access to the full type hierarchy, or if it changes a lot. For this situation, you can register a default serializer.

例如,如果无法访问完整的类型层次结构,或者类型变化很大,有时需要根据实例动态选择多态类型的序列化器。在这种情况下,可以注册一个默认序列化器。

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()
}

We register a default serializer handler using the polymorphicDefaultSerializer function in the SerializersModule { ... } DSL that defines a strategy which takes an instance of the base class and provides a serialization strategy. In the below example we use a when block to check the type of the instance, without ever having to refer to the private implementation classes.

我们使用 SerializersModule { ... } DSL 中的 polymorphicDefaultSerializer 函数注册一个默认的序列化程序处理程序,该函数定义了一个策略,该策略采用基类的实例并提供序列化策略。在下面的示例中,我们使用一个 when 块来检查实例的类型,而无需引用私有实现类。

val module = SerializersModule {
    polymorphicDefaultSerializer(Animal::class) { instance ->
        @Suppress("UNCHECKED_CAST")
        when (instance) {
            is Cat -> CatSerializer as SerializationStrategy<Animal>
            is Dog -> DogSerializer as SerializationStrategy<Animal>
            else -> null
        }
    }
}

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)
    }
  }
}

Using this module we can now serialize instances of Cat and Dog.
使用此模块,我们现在可以序列化 Cat 和 Dog 的实例。

val format = Json { serializersModule = module }

fun main() {
    println(format.encodeToString<Animal>(AnimalProvider.createCat()))
}

You can get the full code here
您可以在此处获取完整代码

{"type":"Cat","catType":"Tabby"}

The next chapter covers JSON features.
下一章将介绍 JSON 功能。