序列化程序¶
This is the third chapter of the Kotlin Serialization Guide. In this chapter we’ll take a look at serializers in more detail, and we’ll see how custom serializers can be written.
这是《Kotlin 序列化指南》的第三章。在本章中,我们将更详细地介绍序列化程序,并了解如何编写自定义序列化程序。
序列化程序简介¶
Introduction to serializers
Formats, like JSON, control the encoding of an object into specific output bytes, but how the object is decomposed into its constituent properties is controlled by a serializer. So far we’ve been using automatically-derived serializers by using the @Serializable
annotation as explained in the Serializable classes section, or using builtin serializers that were shown in the Builtin classes section.
格式(如 JSON)控制着将对象编码为特定输出字节的编码,但如何将对象分解为其组成属性则由序列化器控制。到目前为止,我们一直通过使用 [@Serializable
]注解(如 [Serializable classes] 部分所述)来使用自动派生的序列化器,或者使用 [Builtin classes] 部分所示的内置序列化器。
As a motivating example, let us take the following Color
class with an integer value storing its rgb
bytes.
作为一个激励性的例子,让我们以下面的 Color
类为例,该类具有存储其 rgb
字节的整数值。
@Serializable
class Color(val rgb: Int)
fun main() {
val green = Color(0x00ff00)
println(Json.encodeToString(green))
}
You can get the full code here.
您可以在此处获取完整代码。
By default this class serializes its rgb
property into JSON.
默认情况下,此类将其 rgb
属性序列化为 JSON。
插件生成的序列化程序¶
Plugin-generated serializer
Every class marked with the @Serializable
annotation, like the Color
class from the previous example, gets an instance of the KSerializer interface automatically generated by the Kotlin Serialization compiler plugin. We can retrieve this instance using the .serializer()
function on the class’s companion object.
与上一个示例中的 Color
类一样,每个标有 @Serializable
注释的类都会获得由 Kotlin 序列化编译器插件自动生成的 KSerializer 接口的实例。我们可以使用类的伴随对象上的 .serializer()
函数来检索此实例。
We can examine its descriptor property that describes the structure of the serialized class. We’ll learn more details about that in the upcoming sections.
我们可以检查其描述序列化类结构的描述符属性。我们将在接下来的章节中了解更多详细信息。
fun main() {
val colorSerializer: KSerializer<Color> = Color.serializer()
println(colorSerializer.descriptor)
}
You can get the full code here.
您可以在此处获取完整代码。
This serializer is automatically retrieved and used by the Kotlin Serialization framework when the Color
class is itself serialized, or when it is used as a property of other classes.
当 Color
类本身序列化时,或者当它被用作其他类的属性时,Kotlin 序列化框架会自动检索和使用此序列化程序。
You cannot define your own function
serializer()
on a companion object of a serializable class.
不能在可序列化类的伴随对象上定义自己的函数serializer()
。
插件生成的泛型序列化程序¶
Plugin-generated generic serializer
For generic classes, like the Box
class shown in the Generic classes section, the automatically generated .serializer()
function accepts as many parameters as there are type parameters in the corresponding class. These parameters are of type KSerializer, so the actual type argument’s serializer has to be provided when constructing an instance of a serializer for a generic class.
对于泛型类(如泛型类“部分中显示的 Box
类),自动生成 .serializer()
的函数接受的参数数量与相应类中的类型参数数量一样多。这些参数的类型为 KSerializer,因此在为泛型类构造序列化程序的实例时,必须提供实际类型参数的序列化程序。
@Serializable
@SerialName("Box")
class Box<T>(val contents: T)
fun main() {
val boxedColorSerializer = Box.serializer(Color.serializer())
println(boxedColorSerializer.descriptor)
}
You can get the full code here.
您可以在此处获取完整代码。
As we can see, a serializer was instantiated to serialize a concrete Box<Color>
.
正如我们所看到的,序列化器被实例化为序列化一个具体 Box<Color>
的 .
内置基元序列化程序¶
Builtin primitive serializers
The serializers for the primitive builtin classes can be retrieved using .serializer()
extensions.
可以使用 .serializer()
扩展检索原始内置类的序列化程序。
fun main() {
val intSerializer: KSerializer<Int> = Int.serializer()
println(intSerializer.descriptor)
}
You can get the full code here.
您可以在此处获取完整代码。
构造集合序列化程序¶
Constructing collection serializers
Builtin collection serializers, when needed, must be explicitly constructed using the corresponding functions ListSerializer(), SetSerializer(), MapSerializer(), etc. These classes are generic, so to instantiate their serializer we must provide the serializers for the corresponding number of their type parameters. For example, we can produce a serializer for a List<String>
in the following way.
内置集合序列化程序在需要时,必须使用相应的函数 ListSerializer()、SetSerializer()、MapSerializer() 等显式构造。这些类是泛型的,因此要实例化它们的序列化器,我们必须为其相应数量的类型参数提供序列化器。例如,我们可以按以下方式为 a List<String>
生成序列化程序。
fun main() {
val stringListSerializer: KSerializer<List<String>> = ListSerializer(String.serializer())
println(stringListSerializer.descriptor)
}
You can get the full code here.
您可以在此处获取完整代码。
使用顶级序列化程序功能¶
Using top-level serializer function
When in doubt, you can always use the top-level generic serializer<T>()
function to retrieve a serializer for an arbitrary Kotlin type in your source-code.
如有疑问,您始终可以使用顶级泛型 serializer<T>()
函数在源代码中检索任意 Kotlin 类型的序列化程序。
@Serializable
@SerialName("Color")
class Color(val rgb: Int)
fun main() {
val stringToColorMapSerializer: KSerializer<Map<String, Color>> = serializer()
println(stringToColorMapSerializer.descriptor)
}
You can get the full code here.
您可以在此处获取完整代码。
自定义序列化程序¶
Custom serializers
A plugin-generated serializer is convenient, but it may not produce the JSON we want for such a class as Color
. Let’s study alternatives.
插件生成的序列化程序很方便,但它可能不会为这样的 Color
类生成我们想要的 JSON。让我们研究一下替代方案。
基本类型序列化程序¶
Primitive serializer
We want to serialize the Color
class as a hex string with the green color represented as "00ff00"
. To achieve this, we write an object that implements the KSerializer interface for the Color
class.
我们希望将 Color
类序列化为十六进制字符串,绿色表示为 "00ff00"
。为了实现这一点,我们编写了一个对象来实现 Color
该类的 KSerializer 接口。
object ColorAsStringSerializer : KSerializer<Color> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Color", 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))
}
}
Serializer has three required pieces.
序列化程序有三个必需的部分。
-
The serialize function implements SerializationStrategy. It receives an instance of Encoder and a value to serialize. It uses the
encodeXxx
functions ofEncoder
to represent a value as a sequence of primitives. There is anencodeXxx
for each primitive type supported by serialization. In our example, encodeString is used.serialize 函数实现 SerializationStrategy。它接收 Encoder 的实例和要序列化的值。它使用 的
encodeXxx
Encoder
函数将值表示为基元序列。序列化支持的每个基元类型都有一个encodeXxx
。在我们的示例中,使用了 encodeString。 -
The deserialize function implements DeserializationStrategy. It receives an instance of Decoder and returns a deserialized value. It uses the
decodeXxx
functions ofDecoder
, which mirror the corresponding functions ofEncoder
. In our example decodeString is used.反序列化函数实现 DeserializationStrategy。它接收 Decoder 的实例并返回反序列化值。它使用
Decoder
的decodeXxx
函数 ,这些函数反映了 的Encoder
相应函数。在我们的示例中,使用了 decodeString。 -
The descriptor property must faithfully explain what exactly the
encodeXxx
anddecodeXxx
functions do so that a format implementation knows in advance what encoding/decoding methods they call. Some formats might also use it to generate a schema for the serialized data. For primitive serialization, the PrimitiveSerialDescriptor function must be used with a unique name of the type that is being serialized. PrimitiveKind describes the specificencodeXxx
/decodeXxx
method that is being used in the implementation.描述符属性必须忠实地解释
encodeXxx
anddecodeXxx
函数的确切作用,以便格式实现事先知道它们调用的编码/解码方法。某些格式还可能使用它来生成序列化数据的架构。对于基元序列化,PrimitiveSerialDescriptor 函数必须与要序列化的类型的唯一名称一起使用。PrimitiveKind 描述实现中使用的特定encodeXxx
/decodeXxx
方法。
When the
descriptor
does not correspond to the encoding/decoding methods, then the behavior of the resulting code is unspecified, and may arbitrarily change in future updates.当 与
descriptor
编码/解码方法不对应时,则生成的代码的行为是未指定的,并且可能会在将来的更新中任意更改。
The next step is to bind a serializer to a class. This is done with the @Serializable
annotation by adding the with
property value.
下一步是将序列化程序绑定到类。这是通过添加 with
属性值来 @Serializable
完成注释的。
Now we can serialize the Color
class as we did before.
现在,我们可以像以前一样序列化 Color
该类。
You can get the full code here.
您可以在此处获取完整代码。
We get the serial representation as the hex string we wanted.
我们得到串行表示为我们想要的十六进制字符串。
Deserialization is also straightforward because we implemented the deserialize
method.
反序列化也很简单,因为我们实现了该 deserialize
方法。
@Serializable(with = ColorAsStringSerializer::class)
class Color(val rgb: Int)
fun main() {
val color = Json.decodeFromString<Color>("\"00ff00\"")
println(color.rgb) // prints 65280
}
You can get the full code here.
您可以在此处获取完整代码。
It also works if we serialize or deserialize a different class with Color
properties.
如果我们序列化或反序列化具有 Color
属性的不同类,它也可以工作。
@Serializable(with = ColorAsStringSerializer::class)
data class Color(val rgb: Int)
@Serializable
data class Settings(val background: Color, val foreground: Color)
fun main() {
val data = Settings(Color(0xffffff), Color(0))
val string = Json.encodeToString(data)
println(string)
require(Json.decodeFromString<Settings>(string) == data)
}
You can get the full code here.
您可以在此处获取完整代码。
Both Color
properties are serialized as strings.
这两个 Color
属性都序列化为字符串。
委派序列化程序¶
Delegating serializers
In the previous example, we represented the Color
class as a string. String is considered to be a primitive type, therefore we used PrimitiveClassDescriptor
and specialized encodeString
method. Now let’s see what our actions would be if we have to serialize Color
as another non-primitive type, let’s say IntArray
.
在前面的示例中,我们将 Color
类表示为字符串。字符串被认为是一种原始类型,因此我们使用了 PrimitiveClassDescriptor
专门 encodeString
的方法。现在让我们看看如果我们必须序列化 Color
为另一种非原始类型,我们的操作会是什么,比如 IntArray
说 .
An implementation of KSerializer for our original Color
class is going to perform a conversion between Color
and IntArray
, but delegate the actual serialization logic to the IntArraySerializer
using encodeSerializableValue and decodeSerializableValue.
我们原始 Color
类的 KSerializer 实现将在 和 IntArray
之间 Color
执行转换,但 IntArraySerializer
将实际的序列化逻辑委托给使用 encodeSerializableValue 和 decodeSerializableValue。
import kotlinx.serialization.builtins.IntArraySerializer
class ColorIntArraySerializer : KSerializer<Color> {
private val delegateSerializer = IntArraySerializer()
override val descriptor = SerialDescriptor("Color", delegateSerializer.descriptor)
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)
}
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])
}
}
Note that we can’t use default Color.serializer().descriptor
here because formats that rely on the schema may think that we would call encodeInt
instead of encodeSerializableValue
. Neither we can use IntArraySerializer().descriptor
directly — otherwise, formats that handle int arrays specially can’t tell if value
is really a IntArray
or a Color
. Don’t worry, this optimization would still kick in when serializing actual underlying int array.
请注意,我们不能在这里使用 default Color.serializer().descriptor
,因为依赖于架构的格式可能会认为我们会调用 encodeInt
而不是 encodeSerializableValue
.我们也不能直接使用 IntArraySerializer().descriptor
——否则,专门处理 int 数组的格式无法判断是否真的 value
是 Color
a IntArray
或 .别担心,在序列化实际底层 int 数组时,此优化仍会启动。
Example of how format can treat arrays specially is shown in the formats guide.
格式指南中显示了格式如何专门处理数组的示例。
Now we can use the serializer:
现在我们可以使用序列化器:
@Serializable(with = ColorIntArraySerializer::class)
class Color(val rgb: Int)
fun main() {
val green = Color(0x00ff00)
println(Json.encodeToString(green))
}
As you can see, such array representation is not very useful in JSON, but may save some space when used with a ByteArray
and a binary format.
如您所见,这种数组表示在 JSON 中不是很有用,但在与 a ByteArray
和 binary 格式一起使用时可能会节省一些空间。
You can get the full code here.
您可以在此处获取完整代码。
通过代理项的复合序列化程序¶
Composite serializer via surrogate
Now our challenge is to get Color
serialized so that it is represented in JSON as if it is a class with three properties—r
, g
, and b
—so that JSON encodes it as an object. The easiest way to achieve this is to define a surrogate class mimicking the serialized form of Color
that we are going to use for its serialization. We also set the SerialName of this surrogate class to Color
. Then if any format uses this name the surrogate looks like it is a Color
class. The surrogate class can be private
, and can enforce all the constraints on the serial representation of the class in its init
block.
现在我们的挑战是 Color
序列化,以便用 JSON 表示它,就好像它是一个具有三个属性的类—— r
、 g
和 b
——,以便 JSON 将其编码为对象。实现此目的的最简单方法是定义一个代理项类,该类模仿我们将用于其序列化的序列化形式的 Color
代理项。我们还将此代理项类的 SerialName 设置为 Color
。然后,如果任何格式使用此名称,则代理项看起来像是一个 Color
类。代理项类可以是 private
,并且可以对其 init
块中类的串行表示形式强制执行所有约束。
@Serializable
@SerialName("Color")
private class ColorSurrogate(val r: Int, val g: Int, val b: Int) {
init {
require(r in 0..255 && g in 0..255 && b in 0..255)
}
}
An example of where the class name is used is shown in the Custom subclass serial name section in the chapter on polymorphism.
多态性一章的“自定义子类序列化名称”部分显示了使用类名的示例。
Now we can use the ColorSurrogate.serializer()
function to retrieve a plugin-generated serializer for the surrogate class.
现在,我们可以使用该 ColorSurrogate.serializer()
函数为代理项类检索插件生成的序列化程序。
We can use the same approach as in delegating serializer, but this time, we are fully reusing an automatically generated SerialDescriptor for the surrogate because it should be indistinguishable from the original.
我们可以使用与委派序列化程序相同的方法,但这一次,我们将完全重用自动生成的 SerialDescriptor 作为代理项,因为它应该与原始描述符无法区分。
object ColorSerializer : KSerializer<Color> {
override val descriptor: SerialDescriptor = ColorSurrogate.serializer().descriptor
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)
}
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)
}
}
We bind the ColorSerializer
serializer to the Color
class.
我们将 ColorSerializer
序列化程序绑定到类。 Color
Now we can enjoy the result of serialization for the Color
class.
现在我们可以享受 Color
类的序列化结果了。
You can get the full code here.
您可以在此处获取完整代码。
手写复合序列化程序¶
Hand-written composite serializer
There are some cases where a surrogate solution does not fit. Perhaps we want to avoid the performance implications of additional allocation, or we want a configurable/dynamic set of properties for the resulting serial representation. In these cases we need to manually write a class serializer which mimics the behaviour of a generated serializer.
在某些情况下,替代解决方案不适合。也许我们想避免额外分配对性能的影响,或者我们想要为生成的串行表示提供一组可配置/动态的属性。在这些情况下,我们需要手动编写一个类序列化程序,它模仿生成的序列化程序的行为。
Let’s introduce it piece by piece. First, a descriptor is defined using the buildClassSerialDescriptor builder. The element function in the builder DSL automatically fetches serializers for the corresponding fields by their type. The order of elements is important. They are indexed starting from zero.
让我们一点一点地介绍一下。首先,使用 buildClassSerialDescriptor 生成器定义描述符。生成器 DSL 中的 element 函数会按类型自动获取相应字段的序列化程序。元素的顺序很重要。它们从零开始编制索引。
override val descriptor: SerialDescriptor =
buildClassSerialDescriptor("Color") {
element<Int>("r")
element<Int>("g")
element<Int>("b")
}
The “element” is a generic term here. What is an element of a descriptor depends on its SerialKind. Elements of a class descriptor are its properties, elements of a enum descriptor are its cases, etc.
“元素”在这里是一个通用术语。什么是描述符的元素取决于其 SerialKind。类描述符的元素是它的属性,枚举描述符的元素是它的大小写,等等。
Then we write the serialize
function using the encodeStructure DSL that provides access to the CompositeEncoder in its block. The difference between Encoder and CompositeEncoder is the latter has encodeXxxElement
functions that correspond to the encodeXxx
functions of the former. They must be called in the same order as in the descriptor.
然后,我们使用 encodeStructure DSL 编写函数, serialize
该 DSL 提供对其块中 CompositeEncoder 的访问。Encoder 和 CompositeEncoder 之间的区别在于后者具有 encodeXxxElement
与前者 encodeXxx
的功能相对应的功能。必须按照描述符中的相同顺序调用它们。
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)
}
The most complex piece of code is the deserialize
function. It must support formats, like JSON, that can decode properties in an arbitrary order. It starts with the call to decodeStructure to get access to a CompositeDecoder. Inside it we write a loop that repeatedly calls decodeElementIndex to decode the index of the next element, then we decode the corresponding element using decodeIntElement in our example, and finally we terminate the loop when CompositeDecoder.DECODE_DONE
is encountered.
最复杂的代码是 deserialize
函数。它必须支持可以按任意顺序解码属性的格式,例如 JSON。它从调用 decodeStructure 开始,以获取对 CompositeDecoder 的访问权限。在里面,我们编写了一个循环,反复调用 decodeElementIndex 来解码下一个元素的索引,然后我们在我们的示例中使用 decodeIntElement 解码相应的元素,最后在遇到循环时 CompositeDecoder.DECODE_DONE
终止循环。
override fun deserialize(decoder: Decoder): Color =
decoder.decodeStructure(descriptor) {
var r = -1
var g = -1
var b = -1
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)
}
Now we bind the resulting serializer to the Color
class and test its serialization/deserialization.
现在,我们将生成的序列化程序绑定到类并 Color
测试其序列化/反序列化。
@Serializable(with = ColorAsObjectSerializer::class)
data class Color(val rgb: Int)
fun main() {
val color = Color(0x00ff00)
val string = Json.encodeToString(color)
println(string)
require(Json.decodeFromString<Color>(string) == color)
}
You can get the full code here.
您可以在此处获取完整代码。
As before, we got the Color
class represented as a JSON object with three keys:
和以前一样,我们将 Color
类表示为具有三个键的 JSON 对象:
顺序解码协议(实验性)¶
Sequential decoding protocol (experimental)
The implementation of the deserialize
function from the previous section works with any format. However, some formats either always store all the complex data in order, or only do so sometimes (JSON always stores collections in order). With these formats the complex protocol of calling decodeElementIndex
in the loop is not needed, and a faster implementation can be used if the CompositeDecoder.decodeSequentially function returns true
. The plugin-generated serializers are actually conceptually similar to the below code.
上一节 deserialize
中函数的实现适用于任何格式。但是,某些格式要么始终按顺序存储所有复杂数据,要么仅偶尔这样做(JSON 始终按顺序存储集合)。使用这些格式,不需要在循环中调用 decodeElementIndex
的复杂协议,如果 CompositeDecoder.decodeSequentially 函数返回 true
,则可以使用更快的实现。插件生成的序列化程序实际上在概念上类似于下面的代码。
override fun deserialize(decoder: Decoder): Color =
decoder.decodeStructure(descriptor) {
var r = -1
var g = -1
var b = -1
if (decodeSequentially()) { // sequential decoding protocol
r = decodeIntElement(descriptor, 0)
g = decodeIntElement(descriptor, 1)
b = decodeIntElement(descriptor, 2)
} else 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)
}
You can get the full code here.
您可以在此处获取完整代码。
序列化第三方类¶
Serializing 3rd party classes
Sometimes an application has to work with an external type that is not serializable. Let us use java.util.Date as an example. As before, we start by writing an implementation of KSerializer for the class. Our goal is to get a Date
serialized as a long number of milliseconds following the approach from the Primitive serializer section.
有时,应用程序必须使用不可序列化的外部类型。让我们以 java.util.Date 为例。和以前一样,我们首先为该类编写 KSerializer 的实现。我们的目标是按照 Primitive 序列化程序部分的方法将序列 Date
化为长毫秒。
In the following sections any kind of
Date
serializer would work. For example, if we wantDate
to be serialized as an object, we would use an approach from the Composite serializer via surrogate section.在以下各节中,任何类型的
Date
序列化程序都可以使用。例如,如果我们想Date
序列化为对象,我们将使用 Composite serializer via surrogate 部分的方法。See also Deriving external serializer for another Kotlin class (experimental) when you need to serialize a 3rd-party Kotlin class that could have been serializable, but is not.
另请参阅 当您需要序列化本来可以序列化但不可序列化的第三方 Kotlin 类时,请为另一个 Kotlin 类派生外部序列化器(实验性)。
object DateAsLongSerializer : KSerializer<Date> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG)
override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time)
override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong())
}
We cannot bind the DateAsLongSerializer
serializer to the Date
class with the @Serializable
annotation because we don’t control the Date
source code. There are several ways to work around that.
我们无法将 DateAsLongSerializer
序列化程序绑定到带有 @Serializable
注释的类, Date
因为我们无法控制 Date
源代码。有几种方法可以解决这个问题。
手动传递序列化程序¶
Passing a serializer manually
All encodeToXxx
and decodeFromXxx
functions have an overload with the first serializer parameter. When a non-serializable class, like Date
, is the top-level class being serialized, we can use those.
All encodeToXxx
和 decodeFromXxx
functions 具有第一个序列化程序参数的重载。当不可序列化的类(如 Date
)是要序列化的顶级类时,我们可以使用它们。
fun main() {
val kotlin10ReleaseDate = SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")
println(Json.encodeToString(DateAsLongSerializer, kotlin10ReleaseDate))
}
You can get the full code here.
您可以在此处获取完整代码。
在属性上指定序列化程序¶
Specifying serializer on a property
When a property of a non-serializable class, like Date
, is serialized as part of a serializable class we must supply its serializer or the code will not compile. This is accomplished using the @Serializable
annotation on the property.
当不可序列化类的属性(如 Date
)作为可序列化类的一部分序列化时,我们必须提供其序列化器,否则代码将无法编译。这是使用属性上的 @Serializable
注释完成的。
@Serializable
class ProgrammingLanguage(
val name: String,
@Serializable(with = DateAsLongSerializer::class)
val stableReleaseDate: Date
)
fun main() {
val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00"))
println(Json.encodeToString(data))
}
You can get the full code here.
您可以在此处获取完整代码。
The stableReleaseDate
property is serialized with the serialization strategy that we specified for it:
该 stableReleaseDate
属性使用我们为其指定的序列化策略进行序列化:
为特定类型指定序列化程序¶
Specifying serializer for a particular type
@Serializable
annotation can also be applied directly to the types. This is handy when a class that requires a custom serializer, such as Date
, happens to be a generic type argument. The most common use case for that is when you have a list of dates:
@Serializable
注释也可以直接应用于类型。当需要自定义序列化程序的类(如 Date
)恰好是泛型类型参数时,这很方便。最常见的用例是当您有一个日期列表时:
@Serializable
class ProgrammingLanguage(
val name: String,
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))
}
You can get the full code here.
您可以在此处获取完整代码。
指定文件的序列化程序¶
Specifying serializers for a file
A serializer for a specific type, like Date
, can be specified for a whole source code file with the file-level UseSerializers annotation at the beginning of the file.
可以为整个源代码文件指定特定类型的序列化程序,例如 Date
,文件开头带有文件级 UseSerializers 注释。
Now a Date
property can be used in a serializable class without additional annotations.
现在,可以在可序列化类中使用属性 Date
,而无需添加注释。
@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))
}
You can get the full code here.
您可以在此处获取完整代码。
使用 typealias 全局指定序列化程序¶
Specifying serializer globally using typealias
kotlinx.serialization tends to be the always-explicit framework when it comes to serialization strategies: normally, they should be explicitly mentioned in @Serializable
annotation. Therefore, we do not provide any kind of global serializer configuration (except for context serializer mentioned later).
当涉及到序列化策略时,kotlinx.serialization 往往是始终明确的框架:通常,它们应该在注释中 @Serializable
显式提及。因此,我们不提供任何类型的全局序列化程序配置(后面提到的上下文序列化程序除外)。
However, in projects with a large number of files and classes, it may be too cumbersome to specify @file:UseSerializers
every time, especially for classes like Date
or Instant
that have a fixed strategy of serialization across the project. For such cases, it is possible to specify serializers using typealias
es, as they preserve annotations, including serialization-related ones:
但是,在具有大量文件和类的项目中,每次都指定 @file:UseSerializers
可能太麻烦,尤其是对于像 OR Date
Instant
这样的类,这些类在整个项目中具有固定的序列化策略。对于这种情况,可以使用 typealias
es 指定序列化程序,因为它们会保留注释,包括与序列化相关的注释:
typealias DateAsLong = @Serializable(DateAsLongSerializer::class) Date
typealias DateAsText = @Serializable(DateAsSimpleTextSerializer::class) Date
Using these new different types, it is possible to serialize a Date differently without additional annotations:
使用这些新的不同类型,可以在没有额外注释的情况下以不同的方式序列化 Date:
@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))
}
You can get the full code here.
您可以在此处获取完整代码。
泛型类型的自定义序列化程序¶
Custom serializers for a generic type
Let us take a look at the following example of the generic Box<T>
class. It is marked with @Serializable(with = BoxSerializer::class)
as we plan to have a custom serialization strategy for it.
让我们看一下下面的泛型 Box<T>
类示例。它被标记为 @Serializable(with = BoxSerializer::class)
我们计划为其制定自定义序列化策略。
An implementation of KSerializer for a regular type is written as an object
, as we saw in this chapter’s examples for the Color
type. A generic class serializer is instantiated with serializers for its generic parameters. We saw this in the Plugin-generated generic serializer section. A custom serializer for a generic class must be a class
with a constructor that accepts as many KSerializer parameters as the type has generic parameters. Let us write a Box<T>
serializer that erases itself during serialization, delegating everything to the underlying serializer of its data
property.
常规类型的 KSerializer 实现被写成 ,正如我们在本章的 Color
该类型的示例中看到的那样 object
。泛型类序列化程序使用序列化程序实例化其泛型参数。我们在插件生成的泛型序列化程序部分中看到了这一点。泛型类的自定义序列化程序必须是 class
具有构造函数的构造函数,该构造函数接受与类型具有泛型参数的 KSerializer 参数一样多的 KSerializer 参数。让我们编写一个 Box<T>
序列化程序,该序列化器在序列化过程中擦除自身,将所有内容委托给其 data
属性的基础序列化程序。
class BoxSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<Box<T>> {
override val descriptor: SerialDescriptor = dataSerializer.descriptor
override fun serialize(encoder: Encoder, value: Box<T>) = dataSerializer.serialize(encoder, value.contents)
override fun deserialize(decoder: Decoder) = Box(dataSerializer.deserialize(decoder))
}
Now we can serialize and deserialize Box<Project>
.
现在我们可以序列化和反序列化 Box<Project>
。
@Serializable
data class Project(val name: String)
fun main() {
val box = Box(Project("kotlinx.serialization"))
val string = Json.encodeToString(box)
println(string)
println(Json.decodeFromString<Box<Project>>(string))
}
You can get the full code here.
您可以在此处获取完整代码。
The resulting JSON looks like the Project
class was serialized directly.
生成的 JSON 看起来像是 Project
直接序列化的类。
特定于格式的序列化程序¶
Format-specific serializers
The above custom serializers worked in the same way for every format. However, there might be format-specific features that a serializer implementation would like to take advantage of.
上述自定义序列化程序对每种格式都以相同的方式工作。但是,序列化程序实现可能希望利用特定于格式的功能。
-
The Json transformations section of the Json chapter provides examples of serializers that utilize JSON-specific features.
Json 一章的 Json 转换部分提供了利用 JSON 特定功能的序列化程序的示例。 -
A format implementation can have a format-specific representation for a type as explained in the Format-specific types section of the Alternative and custom formats (experimental) chapter.
格式实现可以具有特定于类型的格式表示形式,如替代格式和自定义格式(实验)一章的特定于格式的类型部分所述。
This chapter proceeds with a generic approach to tweaking the serialization strategy based on the context.
本章继续采用一种通用方法,根据上下文调整序列化策略。
上下文序列化¶
Contextual serialization
All the previous approaches to specifying custom serialization strategies were static, that is fully defined at compile-time. The exception was the Passing a serializer manually approach, but it worked only on a top-level object. You might need to change the serialization strategy for objects deep in the serialized object tree at run-time, with the strategy being selected in a context-dependent way. For example, you might want to represent java.util.Date
in JSON format as an ISO 8601 string or as a long integer depending on a version of a protocol you are serializing data for. This is called contextual serialization, and it is supported by a built-in ContextualSerializer class. Usually we don’t have to use this serializer class explicitly—there is the Contextual annotation providing a shortcut to the @Serializable(with = ContextualSerializer::class)
annotation, or the UseContextualSerialization annotation can be used at the file-level just like the UseSerializers annotation. Let’s see an example utilizing the former.
以前所有指定自定义序列化策略的方法都是静态的,在编译时完全定义。例外情况是手动传递序列化程序方法,但它仅适用于顶级对象。您可能需要在运行时更改序列化对象树深处对象的序列化策略,并以上下文相关的方式选择该策略。例如,您可能希望以 JSON 格式表示 java.util.Date
为 ISO 8601 字符串或长整数,具体取决于要序列化数据的协议版本。这称为上下文序列化,它由内置的 ContextualSerializer 类支持。通常,我们不必显式使用此序列化程序类 - 有 Contextual 注释提供注释的快捷方式 @Serializable(with = ContextualSerializer::class)
,或者 UseContextualSerialization 注释可以在文件级别使用,就像 UseSerializers 注释一样。让我们看一个使用前者的例子。
@Serializable
class ProgrammingLanguage(
val name: String,
@Contextual
val stableReleaseDate: Date
)
To actually serialize this class we must provide the corresponding context when calling the encodeToXxx
/decodeFromXxx
functions. Without it we’ll get a “Serializer for class ‘Date’ is not found” exception.
为了实际序列化此类, encodeToXxx
我们必须在调用 / decodeFromXxx
函数时提供相应的上下文。如果没有它,我们将得到“找不到类’Date’的序列化程序”异常。
See here for an example that produces that exception.
有关生成该异常的示例,请参阅此处。
序列化模块¶
Serializers module
To provide a context, we define a SerializersModule instance that describes which serializers shall be used at run-time to serialize which contextually-serializable classes. This is done using the SerializersModule {} builder function, which provides the SerializersModuleBuilder DSL to register serializers. In the below example we use the contextual function with the serializer. The corresponding class this serializer is defined for is fetched automatically via the reified
type parameter.
为了提供上下文,我们定义了一个 SerializersModule 实例,该实例描述在运行时应使用哪些序列化程序来序列化哪些上下文可序列化的类。这是使用 SerializersModule {} 生成器函数完成的,该函数提供 SerializersModuleBuilder DSL 来注册序列化程序。在下面的示例中,我们将上下文函数与序列化程序一起使用。为此序列化程序定义的相应类是通过 reified
type 参数自动提取的。
Next we create an instance of the Json format with this module using the Json {} builder function and the serializersModule property.
接下来,我们使用 Json {} builder 函数和 serializersModule 属性使用此模块创建 Json 格式的实例。
Details on custom JSON configurations can be found in the JSON configuration section.
有关自定义 JSON 配置的详细信息,请参阅 JSON 配置部分。
Now we can serialize our data with this format
.
现在我们可以用这个 format
序列化我们的数据。
fun main() {
val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00"))
println(format.encodeToString(data))
}
You can get the full code here.
您可以在此处获取完整代码。
上下文序列化和泛型类¶
Contextual serialization and generic classes
In the previous section we saw that we can register serializer instance in the module for a class we want to serialize contextually. We also know that serializers for generic classes have constructor parameters — type arguments serializers. It means that we can’t use one serializer instance for a class if this class is generic:
在上一节中,我们看到我们可以在模块中为要在上下文中序列化的类注册序列化程序实例。我们还知道泛型类的序列化程序具有构造函数参数 — 类型参数序列化程序。这意味着如果一个类是泛型的,我们不能为一个类使用一个序列化程序实例:
val incorrectModule = SerializersModule {
// Can serialize only Box<Int>, but not Box<String> or others
contextual(BoxSerializer(Int.serializer()))
}
For cases when one want to serialize contextually a generic class, it is possible to register provider in the module:
对于想要在上下文中序列化泛型类的情况,可以在模块中注册提供程序:
val correctModule = SerializersModule {
// args[0] contains Int.serializer() or String.serializer(), depending on the usage
contextual(Box::class) { args -> BoxSerializer(args[0]) }
}
Additional details on serialization modules are given in the Merging library serializers modules section of the Polymorphism chapter.
有关序列化模块的其他详细信息,请参阅 Polymorphism 一章的合并库序列化程序模块部分。
为另一个 Kotlin 类派生外部序列化程序(实验性)¶
Deriving external serializer for another Kotlin class (experimental)
If a 3rd-party class to be serialized is a Kotlin class with a properties-only primary constructor, a kind of class which could have been made @Serializable
, then you can generate an external serializer for it using the Serializer annotation on an object with the forClass
property.
如果要序列化的第三方类是具有仅属性主构造函数的 Kotlin 类,这是一种可以创建 @Serializable
的类,那么您可以使用具有该 forClass
属性的对象上的序列化程序注释为其生成外部序列化程序。
// NOT @Serializable
class Project(val name: String, val language: String)
@Serializer(forClass = Project::class)
object ProjectSerializer
You must bind this serializer to a class using one of the approaches explained in this chapter. We’ll follow the Passing a serializer manually approach for this example.
必须使用本章中介绍的方法之一将此序列化程序绑定到类。对于此示例,我们将遵循手动传递序列化程序方法。
fun main() {
val data = Project("kotlinx.serialization", "Kotlin")
println(Json.encodeToString(ProjectSerializer, data))
}
You can get the full code here.
您可以在此处获取完整代码。
This gets all the Project
properties serialized:
这将使所有 Project
属性序列化:
外部序列化使用属性¶
External serialization uses properties
As we saw earlier, the regular @Serializable
annotation creates a serializer so that Backing fields are serialized. External serialization using Serializer(forClass = ...)
has no access to backing fields and works differently. It serializes only accessible properties that have setters or are part of the primary constructor. The following example shows this.
正如我们之前所看到的,常规 @Serializable
注释会创建一个序列化程序,以便序列化 Backing 字段。外部序列化使用 Serializer(forClass = ...)
无法访问后备字段,并且工作方式不同。它仅序列化具有 setter 或属于主构造函数的可访问属性。以下示例演示了这一点。
// NOT @Serializable, will use external serializer
class Project(
// val in a primary constructor -- serialized
val name: String
) {
var stars: Int = 0 // property with getter & setter -- serialized
val path: String // getter only -- not serialized
get() = "kotlin/$name"
private var locked: Boolean = false // private, not accessible -- not serialized
}
@Serializer(forClass = Project::class)
object ProjectSerializer
fun main() {
val data = Project("kotlinx.serialization").apply { stars = 9000 }
println(Json.encodeToString(ProjectSerializer, data))
}
You can get the full code here.
您可以在此处获取完整代码。
The output is shown below.
输出如下所示。
The next chapter covers Polymorphism.
下一章将介绍多态性。