跳转至

序列化的基本使用

This is the first chapter of the Kotlin Serialization Guide. This chapter shows the basic use of Kotlin Serialization and explains its core concepts.
这是《Kotlin 序列化指南》的第一章。本章介绍了 Kotlin 序列化的基本用法,并解释了其核心概念。

基本

Basics

To convert an object tree to a string or to a sequence of bytes, it must come through two mutually intertwined processes. In the first step, an object is serialized—it is converted into a serial sequence of its constituting primitive values. This process is common for all data formats and its result depends on the object being serialized. A serializer controls this process. The second step is called encoding—it is the conversion of the corresponding sequence of primitives into the output format representation. An encoder controls this process. Whenever the distinction is not important, both the terms of encoding and serialization are used interchangeably.

要将对象树转换为字符串或字节序列,它必须经过两个相互交织的过程。在第一步中,对象被序列化,即将其转换为其构成基元值的序列。此过程对于所有数据格式都是通用的,其结果取决于要序列化的对象。序列化程序控制此过程。第二步称为编码,它是将相应的基元序列转换为输出格式表示形式。编码器控制此过程。每当区别不重要时,编码和序列化的术语可以互换使用。

+---------+  Serialization  +------------+  Encoding  +---------------+
| Objects | --------------> | Primitives | ---------> | Output format |
+---------+                 +------------+            +---------------+

The reverse process starts with parsing of the input format and decoding of primitive values, followed by deserialization of the resulting stream into objects. We’ll see details of this process later.

反向过程从解析输入格式和解码基元值开始,然后将生成的流反序列化为对象。我们稍后将看到此过程的详细信息。

For now, we start with JSON encoding.
现在,我们从JSON编码开始。

JSON 编码

JSON encoding

The whole process of converting data into a specific format is called encoding. For JSON we encode data using the Json.encodeToString extension function. It serializes the object that is passed as its parameter under the hood and encodes it to a JSON string.

将数据转换为特定格式的整个过程称为编码。对于 JSON,我们使用 Json.encodeToString 扩展函数对数据进行编码。它序列化在后台作为其参数传递的对象,并将其编码为 JSON 字符串。

Let’s start with a class describing a project and try to get its JSON representation.

让我们从一个描述项目的类开始,并尝试获取其 JSON 表示形式。

class Project(val name: String, val language: String)

fun main() {
    val data = Project("kotlinx.serialization", "Kotlin")
    println(Json.encodeToString(data))
}

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

When we run this code we get the exception.
当我们运行此代码时,我们得到异常。

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

Serializable classes have to be explicitly marked. Kotlin Serialization does not use reflection, so you cannot accidentally deserialize a class which was not supposed to be serializable. We fix it by adding the @Serializable annotation.

通过添加 @Serializable 注解来标明此类是一个可序列化类。Kotlin 序列化不使用反射,因此您不会意外地反序列化不应该可序列化的类。

@Serializable
class Project(val name: String, val language: String)

fun main() {
    val data = Project("kotlinx.serialization", "Kotlin")
    println(Json.encodeToString(data))
}

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

The @Serializable annotation instructs the Kotlin Serialization plugin to automatically generate and hook up a serializer for this class. Now the output of the example is the corresponding JSON.

@Serializable 注解会使 “Kotlin 序列化插件” 自动生成并挂接此类的序列化程序。此时,示例的输出不会报错了,会输出相应的 JSON。

{"name":"kotlinx.serialization","language":"Kotlin"}

There is a whole chapter about the Serializers. For now, it is enough to know that they are automatically generated by the Kotlin Serialization plugin.
有一整章是关于序列化程序的。现在,只要知道它们是由 Kotlin 序列化插件自动生成的就足够了。

JSON解码

JSON decoding 

The reverse process is called decoding. To decode a JSON string into an object, we’ll use the Json.decodeFromString extension function. To specify which type we want to get as a result, we provide a type parameter to this function.

反向过程称为解码(反序列化)。若要将 JSON 字符串解码为对象,我们将使用 Json.decodeFromString 扩展函数。为了指定我们想要获得的类型,我们为此函数提供了一个类型参数。

As we’ll see later, serialization works with different kinds of classes. Here we are marking our Project class as a data class, not because it is required, but because we want to print its contents to verify how it decodes.

正如我们稍后将看到的,序列化可用于不同类型的类。在这里,我们将我们的 Project 类标记为 data class ,不是因为它是必需的,而是因为我们想要打印其内容以验证它是如何解码的。

@Serializable
data class Project(val name: String, val language: String)

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization","language":"Kotlin"}
    """)
    println(data)
}

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

Running this code we get back the object.
运行此代码,将得到序列化之后的对象。

Project(name=kotlinx.serialization, language=Kotlin)

可序列化类

Serializable classes 

This section goes into more details on how different @Serializable classes are handled.
本节将更详细地介绍如何处理不同的 @Serializable 类。

幕后字段可序列化

Backing fields are serialized

Only a class’s properties with backing fields are serialized, so properties with a getter/setter that don’t have a backing field and delegated properties are not serialized, as the following example shows.

Note

backing fields:很多译文将其翻译为幕后属性幕后字段
如果在kotlin类中定义一个成员变量,Kotlin将自动生成默认setter/getter方法。而Kotlin提供了一种非常特殊的方式声明setter/getter方法:

  var name: String? = null
        //setter方法
        set(value) {
            field = value
        }
        //getter方法
        get() = field

setter和getter中的field字段就是backing fields。

所以,文档上讲有backing fields == 有setter和getter方法

在某个类中,具有setter和getter的属性才会被序列化。不具有 geter/setter 和委托属性的属性都不会被序列化,如以下示例所示。

@Serializable
class Project(
    // name is a property with backing field -- serialized
    // name 具有backing fields --可序列化
    var name: String
) {
    // property with a backing field -- serialized
    // stars 具有backing fields --可序列化
    var stars: Int = 0 

    // getter only, no backing field -- not serialized
    // 只有getter,没有setter,不能被序列化
    val path: String 
        get() = "kotlin/$name"

    // delegated property -- not serialized
    // 委托属性 --不可被序列化
    var id by ::name 
}

fun main() {
    val data = Project("kotlinx.serialization").apply { stars = 9000 }
    println(Json.encodeToString(data))
}

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

We can clearly see that only the name and stars properties are present in the JSON output.
我们可以清楚地看到,JSON 输出中只存在 name and stars 属性。

{"name":"kotlinx.serialization","stars":9000}

构造函数属性要求

Constructor properties requirement

If we want to define the Project class so that it takes a path string, and then deconstructs it into the corresponding properties, we might be tempted to write something like the code below.

如果我们想定义一个类:Project ,以便将构造函数中传入的字符串转换为相应的属性,我们可能会想编写如下代码:

@Serializable
class Project(path: String) {
    val owner: String = path.substringBefore('/')
    val name: String = path.substringAfter('/')
}

This class does not compile because the @Serializable annotation requires that all parameters of the class’s primary constructor be properties. A simple workaround is to define a private primary constructor with the class’s properties, and turn the constructor we wanted into the secondary one.

此类无法被编译,因为 @Serializable 注解要求类的主构造函数的所有参数都是属性。一个简单的解决方法是:定义一个具有所需字段的私有的主构造函数,并使用辅助构造函数间接调用主构造函数。

@Serializable
class Project private constructor(val owner: String, val name: String) {
    constructor(path: String) : this(
        owner = path.substringBefore('/'),
        name = path.substringAfter('/')
    )

    val path: String
        get() = "$owner/$name"
}

Serialization works with a private primary constructor, and still serializes only backing fields.

对于这样具有私有构造函数的类,序列化和反序列化可以正常工作,而且,依旧只能序列化和反序列化具有幕后字段的属性

fun main() {
    println(Json.encodeToString(Project("kotlin/kotlinx.serialization")))
}

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

This example produces the expected output.
此示例生成预期的输出。

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

数据校验

Data validation

Another case where you might want to introduce a primary constructor parameter without a property is when you want to validate its value before storing it to a property. To make it serializable you shall replace it with a property in the primary constructor, and move the validation to an init { ... } block.

另一种可能,想在不创建属性的情况下,让主构造器接收数据,并在其存储到属性之前校验传入数据。为了使其可序列化,你应该在主构造器中用属性替换它,并将验证移动到init { ... }块中。

@Serializable
class Project(val name: String) {
    init {
        require(name.isNotEmpty()) { "name cannot be empty" }
    }
}

A deserialization process works like a regular constructor in Kotlin and calls all init blocks, ensuring that you cannot get an invalid class as a result of deserialization. Let’s try it.

在 Kotlin 中,反序列化过程与常规构造器的工作方式类似,它会调用所有的 init 块,确保反序列化后不会得到无效的类实例。让我们试试吧。

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":""}
    """)
    println(data)
}

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

Running this code produces the exception:
运行此代码将生成异常:

Exception in thread "main" java.lang.IllegalArgumentException: name cannot be empty

可选属性

Optional properties

An object can be deserialized only when all its properties are present in the input. For example, run the following code.

只有当输入中存在对象的所有属性时,才能反序列化对象。例如,运行以下代码。

@Serializable
data class Project(val name: String, val language: String)

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization"}
    """)
    println(data)
}

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

It produces the exception:
它会产生异常:

Exception in thread "main" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'example.exampleClasses04.Project', but it was missing at path: $

This problem can be fixed by adding a default value to the property, which automatically makes it optional for serialization.

可以通过向属性添加默认值来解决此问题,该值会自动使其成为序列化的可选值。

@Serializable
data class Project(val name: String, val language: String = "Kotlin")

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization"}
    """)
    println(data)
}

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

It produces the following output with the default value for the language property.
它将生成以下输出,其中包含 language 该属性的默认值。

Project(name=kotlinx.serialization, language=Kotlin)

可选属性初始化器调用

Optional property initializer call

Note

可选属性初始化器调用(Optional property initializer call)是指在 Kotlin 中,对于类的属性,你可以选择性地提供一个初始化器(initializer),这个初始化器会在构造器执行时初始化属性的值。如果某个属性不是必需的,或者其初始化依赖于某些条件,那么你可以将其初始化器设为可选的。这意味着你可以选择在特定的构造器或初始化块中初始化该属性,而不是在声明属性时就立即初始化。

When an optional property is present in the input, the corresponding initializer for this property is not even called. This is a feature designed for performance, so be careful not to rely on side effects in initializers. Consider the example below.

当输入中存在可选属性时,该属性的相应初始化器不会被调用。这是一项专为提高性能而设计的功能,因此请注意不要依赖初始值设定项中的附带效应。请看下面的例子。

fun computeLanguage(): String {
    println("Computing")
    return "Kotlin"
}

@Serializable
data class Project(val name: String, val language: String = computeLanguage())

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization","language":"Kotlin"}
    """)
    println(data)
}

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

Since the language property was specified in the input, we don’t see the “Computing” string printed in the output.
由于该 language 属性是在输入中指定的,因此我们看不到输出中打印的“Computing”字符串。

Project(name=kotlinx.serialization, language=Kotlin)

必需的属性

Required properties

A property with a default value can be required in a serial format with the @Required annotation. Let us change the previous example by marking the language property as @Required.
一个带有默认值的属性可以通过使用@Required注解在序列化格式中被标记为必需的。例如:把之前例子中language属性标记为@Required

@Serializable
data class Project(val name: String, @Required val language: String = "Kotlin")

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization"}
    """)
    println(data)
}

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

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

Exception in thread "main" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'example.exampleClasses07.Project', but it was missing at path: $

忽略某属性序列化

Transient properties

A property can be excluded from serialization by marking it with the @Transient annotation (don’t confuse it with kotlin.jvm.Transient). Transient properties must have a default value.

一个属性可以通过使用[@Transient]注解来从序列化过程中排除(不要与[kotlin.jvm.Transient]混淆)。被标记为Transient的属性必须有一个默认值。

Note

@Transient是一个用于Kotlin或Java对象序列化时的注解,它告诉序列化器忽略该属性,即使该对象的其他部分正在被序列化。这在你不希望某些字段(比如敏感信息或临时状态)被持久化或通过网络传输时非常有用。
此外,Transient属性必须有一个默认值,这是因为序列化过程不包括这些属性,所以在反序列化时,如果没有默认值,这些属性可能会是未定义的或者不可预测的。
注意,kotlin.jvm.Transient是Kotlin语言特有的,用于标记在JVM平台上应该忽略的属性,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程。 通常用在Fragment调转传值, Bundle中的使用.

@Serializable
data class Project(val name: String, @Transient val language: String = "Kotlin")

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization","language":"Kotlin"}
    """)
    println(data)
}

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

Attempts to explicitly specify its value in the serial format, even if the specified value is equal to the default one, produces the following exception.

在序列化格式中尝试显式指定@Transient属性的值,即使指定的值等于默认值,也会引发以下异常。

Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 42: Encountered an unknown key 'language' at path: $.name
Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys.

The ‘ignoreUnknownKeys’ feature is explained in the Ignoring Unknown Keys section section.
“ignoreUnknownKeys”功能在“忽略未知键”部分部分进行了说明。

Note

其实就是说,@Transient标记的属性不会被序列化和反序列化,但是呢,他在类中还必须要有默认值。序列化的时候生成的json中不会有这个属性。反序列化时,json中有这个属性,反序列化会报错。
如果反序列化时,json中有这个属性的值,又不想报错,需要给Json配置ignoreUnknownKeys属性为true

默认情况下不对默认值进行编码

Defaults are not encoded by default

Default values are not encoded by default in JSON. This behavior is motivated by the fact that in most real-life scenarios such configuration reduces visual clutter, and saves the amount of data being serialized.

默认情况下,序列化之后,默认值不会出现在json字符串中。这种行为的动机是,在大多数现实生活中,这种配置可以减少视觉混乱,并节省要序列化的数据量。

@Serializable
data class Project(val name: String, val language: String = "Kotlin")

fun main() {
    val data = Project("kotlinx.serialization")
    println(Json.encodeToString(data))
}

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

It produces the following output, which does not have the language property because its value is equal to the default one.

它生成以下输出,该输出没有该 language 属性,因为它的值等于默认值。

{"name":"kotlinx.serialization"}

See JSON’s Encoding defaults section on how this behavior can be configured for JSON. Additionally, this behavior can be controlled without taking format settings into account. For that purposes, EncodeDefault annotation can be used:

请参阅 JSON 的编码默认值部分,了解如何为 JSON 配置此行为。此外,可以在不考虑格式设置的情况下控制此行为。为此,可以使用 EncodeDefault 注释:

@Serializable
data class Project(
    val name: String,
    @EncodeDefault val language: String = "Kotlin"
)

This annotation instructs the framework to always serialize property, regardless of its value or format settings. It’s also possible to tweak it into the opposite behavior using EncodeDefault.Mode parameter:

此注解指示框架始终序列化属性,而不考虑其值或格式设置。也可以使用 EncodeDefault.Mode 参数将其调整为相反的行为:

@Serializable
data class User(
    val name: String,
    @EncodeDefault(EncodeDefault.Mode.NEVER) val projects: List<Project> = emptyList()
)

fun main() {
    val userA = User("Alice", listOf(Project("kotlinx.serialization")))
    val userB = User("Bob")
    println(Json.encodeToString(userA))
    println(Json.encodeToString(userB))
}

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

As you can see, language property is preserved and projects is omitted:

正如你所看到的, language 属性被保留并被 projects 省略:

{"name":"Alice","projects":[{"name":"kotlinx.serialization","language":"Kotlin"}]}
{"name":"Bob"}

可为 null 的属性

Nullable properties

Nullable properties are natively supported by Kotlin Serialization.
Kotlin 序列化天然支持可为 null 的属性。

@Serializable
class Project(val name: String, val renamedTo: String? = null)

fun main() {
    val data = Project("kotlinx.serialization")
    println(Json.encodeToString(data))
}

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

This example does not encode null in JSON because Defaults are not encoded.
此示例不以 JSON 编码 null ,因为未对默认值进行编码。

{"name":"kotlinx.serialization"}

强制执行类型安全

Type safety is enforced

Kotlin Serialization strongly enforces the type safety of the Kotlin programming language. In particular, let us try to decode a null value from a JSON object into a non-nullable Kotlin property language.

Kotlin 序列化狠狠的加强了 Kotlin 编程语言的类型安全性。特别是,让我们尝试将 JSON 对象中的 null 值解码为不可为 null 的 Kotlin 属性 language 。

@Serializable
data class Project(val name: String, val language: String = "Kotlin")

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization","language":null}
    """)
    println(data)
}

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

Even though the language property has a default value, it is still an error to attempt to assign the null value to it.

即使该 language 属性具有默认值,尝试为其赋 null 值仍然出错。

Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 52: Expected string literal but 'null' literal was found at path: $.language
Use 'coerceInputValues = true' in 'Json {}' builder to coerce nulls if property has a default value.

It might be desired, when decoding 3rd-party JSONs, to coerce null to a default value. The corresponding feature is explained in the Coercing input values section.
在解码第三方 JSON 时,可能需要强制 null 使用默认值。强制输入值部分介绍了相应的功能。

引用对象

Referenced objects

Serializable classes can reference other classes in their serializable properties. The referenced classes must be also marked as @Serializable.

可序列化类可以在其可序列化属性中引用其他类。引用的类也必须标记为 @Serializable 。

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

@Serializable
class User(val name: String)

fun main() {
    val owner = User("kotlin")
    val data = Project("kotlinx.serialization", owner)
    println(Json.encodeToString(data))
}

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

When encoded to JSON it results in a nested JSON object.
当编码为 JSON 时,它会导致嵌套的 JSON 对象。

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

References to non-serializable classes can be marked as Transient properties, or a custom serializer can be provided for them as shown in the Serializers chapter.
对不可序列化类的引用可以标记为瞬态属性,也可以为它们提供自定义序列化程序,如序列化程序一章中所示。

不压缩重复引用

No compression of repeated references

Kotlin Serialization is designed for encoding and decoding of plain data. It does not support reconstruction of arbitrary object graphs with repeated object references. For example, let us try to serialize an object that references the same owner instance twice.

Kotlin 序列化专为对纯数据进行编码和解码而设计。它不支持使用重复对象引用重建任意对象图。例如,让我们尝试序列化两次引用同一 owner 实例的对象。

@Serializable
class Project(val name: String, val owner: User, val maintainer: User)

@Serializable
class User(val name: String)

fun main() {
    val owner = User("kotlin")
    val data = Project("kotlinx.serialization", owner, owner)
    println(Json.encodeToString(data))
}

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

We simply get the owner value encoded twice.
owner 的值被编码了两次

{"name":"kotlinx.serialization","owner":{"name":"kotlin"},"maintainer":{"name":"kotlin"}}

Attempt to serialize a circular structure will result in stack overflow. You can use the Transient properties to exclude some references from serialization.
尝试序列化闭环结构将导致堆栈溢出。可以使用 Transient 属性从序列化中排除某些引用。

泛型类

Generic classes

Generic classes in Kotlin provide type-polymorphic behavior, which is enforced by Kotlin Serialization at compile-time. For example, consider a generic serializable class Box<T>.

Kotlin 中的泛型类提供类型多态行为,该行为在编译时由 Kotlin 序列化强制执行。例如,考虑一个泛型可序列化类 Box<T> 。

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

The Box<T> class can be used with builtin types like Int, as well as with user-defined types like Project.
该 Box<T> 类可以与内置类型(如 Int类型)以及用户定义类型(如 Project )一起使用。

@Serializable
class Data(
    val a: Box<Int>,
    val b: Box<Project>
)

fun main() {
    val data = Data(Box(42), Box(Project("kotlinx.serialization", "Kotlin")))
    println(Json.encodeToString(data))
}

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

The actual type that we get in JSON depends on the actual compile-time type parameter that was specified for Box.
我们在 JSON 中获得的实际类型取决于为 Box 指定的实际编译时类型参数。

{"a":{"contents":42},"b":{"contents":{"name":"kotlinx.serialization","language":"Kotlin"}}}

If the actual generic type is not serializable a compile-time error will be produced.
如果实际的泛型类型不可序列化,则将产生编译时错误。

序列化字段名称

Serial field names

The names of the properties used in encoded representation, JSON in our examples, are the same as their names in the source code by default. The name that is used for serialization is called a serial name, and can be changed using the @SerialName annotation. For example, we can have a language property in the source with an abbreviated serial name.

默认情况下,序列化过程中使用的属性名称(在我们的示例中为 JSON)与源代码中的名称相同。用于序列化的名称称为“序列名称”,可以使用 @SerialName 注解进行更改。例如,我们可以在源代码中有一个具有缩写序列名称的 language 属性。

@Serializable
class Project(val name: String, @SerialName("lang") val language: String)

fun main() {
    val data = Project("kotlinx.serialization", "Kotlin")
    println(Json.encodeToString(data))
}

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

Now we see that an abbreviated name lang is used in the JSON output.
现在我们看到 JSON 输出中使用了缩写名称 lang 。

{"name":"kotlinx.serialization","lang":"Kotlin"}

The next chapter covers Builtin classes.
下一章将介绍内置类。