文章

Kotlin序列化多态

Kotlin序列化多态

Kotlin序列化多态

关于Kt多态简述

当在序列化中出现多态时,会在序列化结果中带上类判别器classDiscriminator),以区分不同的class。默认为type,并且可以通过classDiscriminator修改。也可以通过classDiscriminatorMode修改何时添加判别器

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@file:OptIn(ExperimentalSerializationApi::class)  
  
import kotlinx.serialization.ExperimentalSerializationApi  
import kotlinx.serialization.Serializable  
import kotlinx.serialization.encodeToString  
import kotlinx.serialization.json.ClassDiscriminatorMode  
import kotlinx.serialization.json.Json  
  
@Serializable  
sealed interface A  
  
@Serializable  
data class B(val some: String = "B"): A  
  
@Serializable  
data class C(val some: String = "C"): A  
  
fun main()  
{  
    println(Json.encodeToString(B("b") as A))  
    var json = Json()  
    {  
        classDiscriminator = "custom_class_discriminator"  
    }  
    println(json.encodeToString(C("c") as A))  
    json = Json(json)  
    {  
        classDiscriminatorMode = ClassDiscriminatorMode.NONE  
    }  
    println(json.encodeToString(C("c") as A))  
}
0
1
2
{"type":"B","some":"b"}
{"custom_class_discriminator":"C","some":"c"}
{"some":"c"}

不同类的类判别器特殊对待

但有时我们会希望使用一个json对象对于不同的类时而带有类判别器,时而不带(例如在ktor项目中的内容协商,整个项目不得不使用同一个json对象进行序列化)。 例如:由于A有反序列需求,我们希望A带有类判别器,而B不带。

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import kotlinx.serialization.Serializable  
import kotlinx.serialization.encodeToString  
import kotlinx.serialization.json.Json  
  
@Serializable  
sealed interface A  
  
@Serializable  
data class AB(val some: String = "B"): A  
  
@Serializable  
data class AC(val some: String = "C"): A  
  
@Serializable  
sealed interface B  
@Serializable  
data class BB(val some: String = "B"): B  
  
@Serializable  
data class BC(val some: String = "C"): B  
  
fun main()  
{  
    val json = Json()  
    {  
        // TODO  
    }  
	println(json.encodeToString(AB() as A)) // 期望: {type: "AB", some: "B"}
    println(json.encodeToString(AC() as A)) // 期望: {type: "AC", some: "C"}
    println(json.encodeToString(BB() as B)) // 期望: {some: "B"}
    println(json.encodeToString(BC() as B)) // 期望: {some: "C"}
}

方法1

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import kotlinx.serialization.*  
import kotlinx.serialization.descriptors.PolymorphicKind  
import kotlinx.serialization.descriptors.SerialDescriptor  
import kotlinx.serialization.descriptors.buildSerialDescriptor  
import kotlinx.serialization.encoding.Decoder  
import kotlinx.serialization.encoding.Encoder  
import kotlinx.serialization.json.*  
  
@OptIn(ExperimentalSerializationApi::class)  
@Serializable(A.ASerializer::class)  
sealed interface A  
{  
    object ASerializer: KSerializer<A>  
    {  
        @OptIn(InternalSerializationApi::class)  
        override val descriptor: SerialDescriptor =  
            buildSerialDescriptor("A", PolymorphicKind.SEALED)  
            {  
                element("AB", AB.serializer().descriptor)  
                element("AC", AC.serializer().descriptor)  
            }  
  
        override fun serialize(encoder: Encoder, value: A)  
        {  
            when(value)  
            {  
                is AB -> encoder.encodeSerializableValue(AB.serializer(), value)  
                is AC -> encoder.encodeSerializableValue(AC.serializer(), value)  
            }  
        }  
  
        override fun deserialize(decoder: Decoder): A  
        {  
            val element = decoder.decodeSerializableValue(JsonElement.serializer())  
            val some = element.jsonObject["some"]?.jsonPrimitive  
            return if (some?.content == "B" && some.isString == true) AB() else AC()  
        }  
    }  
}  
@Serializable  
data class AB(val some: String = "B"): A  
@Serializable  
data class AC(val some: String = "C"): A  
  
@Serializable  
sealed interface B  
@Serializable  
data class BB(val some: String = "B"): B  
@Serializable  
data class BC(val some: String = "C"): B  
  
@OptIn(ExperimentalSerializationApi::class)  
fun main()  
{  
    val json = Json()  
    {  
        encodeDefaults = true  
        classDiscriminatorMode = ClassDiscriminatorMode.NONE  
    }  
  
    println(json.encodeToString(AB() as A)) // 期望: {type: "AB", some: "B"}   
    println(json.encodeToString(AC() as A)) // 期望: {type: "AC", some: "C"}   
    println(json.encodeToString(BB() as B)) // 期望: {some: "B"}   
    println(json.encodeToString(BC() as B)) // 期望: {some: "C"}  
    println(json.decodeFromString<A>(json.encodeToString(AB() as A)))  
    runCatching {  
        println(json.decodeFromString<B>(json.encodeToString(BB() as B)))  
    }.onFailure {  
        println(it)  
    }  
}
0
1
2
3
4
5
6
{"some":"B"}
{"some":"C"}
{"some":"B"}
{"some":"C"}
AB(some=B)
kotlinx.serialization.json.internal.JsonDecodingException: Class discriminator was missing and no default serializers were registered in the polymorphic scope of 'B'.
JSON input: {"some":"B"}

最简单直接的解决方案,自己实现KSerializer解决几乎所有问题

方法2

关闭类判别器,并手动添加type类型

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import kotlinx.serialization.ExperimentalSerializationApi  
import kotlinx.serialization.Serializable  
import kotlinx.serialization.encodeToString  
import kotlinx.serialization.json.ClassDiscriminatorMode  
import kotlinx.serialization.json.Json  
  
@Serializable  
sealed interface A  
  
@Serializable  
data class AB(val some: String = "B"): A  
{  
    val type = "AB"  
}  
  
@Serializable  
data class AC(val some: String = "C"): A  
{  
    val type = "AC"  
}  
  
@Serializable  
sealed interface B  
@Serializable  
data class BB(val some: String = "B"): B  
  
@Serializable  
data class BC(val some: String = "C"): B  
  
@OptIn(ExperimentalSerializationApi::class)  
fun main()  
{  
    val json = Json()  
    {  
        encodeDefaults = true  
        classDiscriminatorMode = ClassDiscriminatorMode.NONE  
    }  
    println(json.encodeToString(AB() as A)) // 期望: {type: "AB", some: "B"}
    println(json.encodeToString(AC() as A)) // 期望: {type: "AC", some: "C"}   
    println(json.encodeToString(BB() as B)) // 期望: {some: "B"}   
    println(json.encodeToString(BC() as B)) // 期望: {some: "C"}  
    println(json.decodeFromString<A>(json.encodeToString(AB() as A)))  
    runCatching {  
        println(json.decodeFromString<B>(json.encodeToString(BB() as B)))  
    }.onFailure {  
        println(it)  
    }  
}
0
1
2
3
4
5
6
{"some":"B","type":"AB"}
{"some":"C","type":"AC"}
{"some":"B"}
{"some":"C"}
AB(some=B)
kotlinx.serialization.json.internal.JsonDecodingException: Class discriminator was missing and no default serializers were registered in the polymorphic scope of 'B'.
JSON input: {"some":"B"}

可以看到,输出与期望相符,并且A类也可以正确反序列化。 并且通过这种方式还可以使反序列类型和序列化的类型不同

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import kotlinx.serialization.ExperimentalSerializationApi  
import kotlinx.serialization.Serializable  
import kotlinx.serialization.encodeToString  
import kotlinx.serialization.json.ClassDiscriminatorMode  
import kotlinx.serialization.json.Json

@Serializable
sealed interface A
@Serializable
data class AB(val some: String = "B"): A
{  
    val type = "AC"  
}
@Serializable
data class AC(val some: String = "C"): A
{  
    val type = "AC"  
}

@Serializable
sealed interface B  
@Serializable
data class BB(val some: String = "B"): B
@Serializable
data class BC(val some: String = "C"): B
  
@OptIn(ExperimentalSerializationApi::class)  
fun main()  
{  
    val json = Json()  
    {  
        encodeDefaults = true  
        classDiscriminatorMode = ClassDiscriminatorMode.NONE  
    }  
    println(json.encodeToString(AB() as A)) // 期望: {type: "AB", some: "B"}    
    println(json.encodeToString(AC() as A)) // 期望: {type: "AC", some: "C"}    
    println(json.encodeToString(BB() as B)) // 期望: {some: "B"}    
    println(json.encodeToString(BC() as B)) // 期望: {some: "C"}  
    println(json.decodeFromString<A>(json.encodeToString(AB() as A)))  
    runCatching {  
        println(json.decodeFromString<B>(json.encodeToString(BB() as B)))  
    }.onFailure {  
        println(it)  
    }  
}
0
1
2
3
4
5
6
{"some":"B","type":"AC"}
{"some":"C","type":"AC"}
{"some":"B"}
{"some":"C"}
AC(some=B)
kotlinx.serialization.json.internal.JsonDecodingException: Class discriminator was missing and no default serializers were registered in the polymorphic scope of 'B'.
JSON input: {"some":"B"}

同时,也可以与JsonClassDiscriminator配合

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import kotlinx.serialization.ExperimentalSerializationApi  
import kotlinx.serialization.Serializable  
import kotlinx.serialization.encodeToString  
import kotlinx.serialization.json.ClassDiscriminatorMode  
import kotlinx.serialization.json.Json  
import kotlinx.serialization.json.JsonClassDiscriminator  
  
@Serializable  
@JsonClassDiscriminator("custom")  
sealed interface A  
@Serializable  
data class AB(val some: String = "B"): A  
{  
    val custom = "AB"  
}  
@Serializable  
data class AC(val some: String = "C"): A  
{  
    val custom = "AC"  
}  
  
@Serializable  
sealed interface B  
@Serializable  
data class BB(val some: String = "B"): B  
@Serializable  
data class BC(val some: String = "C"): B  
  
@OptIn(ExperimentalSerializationApi::class)  
fun main()  
{  
    val json = Json()  
    {  
        encodeDefaults = true  
        classDiscriminatorMode = ClassDiscriminatorMode.NONE  
    }  
    println(json.encodeToString(AB() as A)) // 期望: {type: "AB", some: "B"}
    println(json.encodeToString(AC() as A)) // 期望: {type: "AC", some: "C"}   
    println(json.encodeToString(BB() as B)) // 期望: {some: "B"}   
    println(json.encodeToString(BC() as B)) // 期望: {some: "C"}  
    println(json.decodeFromString<A>(json.encodeToString(AB() as A)))  
    runCatching {  
        println(json.decodeFromString<B>(json.encodeToString(BB() as B)))  
    }.onFailure {  
        println(it)  
    }  
}
0
1
2
3
4
5
6
{"some":"B","custom":"AB"}
{"some":"C","custom":"AC"}
{"some":"B"}
{"some":"C"}
AB(some=B)
kotlinx.serialization.json.internal.JsonDecodingException: Class discriminator was missing and no default serializers were registered in the polymorphic scope of 'B'.
JSON input: {"some":"B"}
本文由作者按照 CC BY-NC 4.0 进行授权, 未经许可不得转载。