我正在尝试序列化子类的实例,但输出是意外的。
这是代码(第二个测试与第一个测试仅在
cc: ${c as Child}
处不同):
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.junit.Test
class SerializeTest {
@Test
fun one() {
println("test: one")
val c: Parent = Child("HI")
println("c: $c")
val en = Json.encodeToString(c)
println("json: $en")
val de: Parent = Json.decodeFromString(en)
println("de: $de")
}
@Test
fun two() {
println("test: two")
val c: Parent = Child("HI")
println("c: $c, cc: ${c as Child}")
val en = Json.encodeToString(c)
println("json: $en")
val de: Parent = Json.decodeFromString(en)
println("de: $de")
}
}
@Serializable
sealed class Parent(val integer: Int)
@Serializable
class Child (val string: String): Parent(0)
输出:
test: one
c: com.example.CC@415b0b49
json: {"type":"com.example.CC","integer":0,"string":"HI"}
de: com.example.CC@3e6104fc
test: two
c: com.example.CC@62010f5c, cc: com.example.CC@62010f5c
json: {"integer":0,"string":"HI"}
Exception: Polymorphic serializer was not found for missing class discriminator ('null')
JSON input: {"integer":0,"string":"HI"}
...
第一个测试运行良好,但第二个测试失败并出现异常。
任何人都可以解释为什么转换:
c as Child
(编译器已经隐式意识到)导致编码函数在第二次测试中省略type
?
因为图书馆以任何其他方式工作都是不切实际的。
在某种程度上,你可以说:
type
被省略,因为库就是这样写的。此行为有明确记录。
序列化多态类层次结构时,必须确保序列化对象的编译时类型是多态类型,而不是具体类型。
但是为什么会这样呢?好吧,想想
encodeToString
函数是如何工作的。调用时,它只知道要求序列化的对象的类型。如果该类型是 Child
,它可以直接将对象序列化为 Child
,工作就完成了。
那么,如果编写该库是为了尝试发现
Child
是 Parent
的子类,并相应地进行序列化,又会怎样呢?实际上这可能是不可能的,因为 Child
和 Parent
可能规定与自动生成的序列化器不同的序列化器。如果它们发生冲突,图书馆如何知道选择哪一个?它不能轻易保证两者的兼容性。
此外,
Child
可以有多个父类。它应该序列化为哪一个或哪些?
如果您不尝试支持这样的功能,所有这些问题都可以避免。
现在,当
encodeToString
看到的类型是 Parent
时,函数可以轻松发现它正在处理一个密封类(有一个简单的属性),然后添加 type
鉴别器以促进多态序列化。
您可能知道,如果
Parent
未密封,则不会自动添加类型鉴别器。为此,您必须注册 Child
进行多态序列化。希望您可以看到,以这种方式注册类也提供了 encodeToString
一种了解是否需要添加类型鉴别器的简单方法。
总而言之,Kotlin 团队对其库的工作方式做出了实际的选择。