Kotlinx.serialization 对多态对象(sealed class )支持更好用
为什么需要「多态对象」?
多个数据类型共享一个父类型时,我们希望都能用 同一种方式 序列化/反序列化。
现实类比:
比如消息系统里同属于 Message,但内容不同:
| 类型 | 数据结构 |
|---|---|
| 文本消息 | { "type": "text", "content": "hello" } |
| 图片消息 | { "type": "image", "url": "xxx.png" } |
这些都是 Message,但字段不同。
你想写一个 List<Message>:
val list: List<Message> = listOf(TextMessage("hello"),ImageMessage("https://img.png")
)
但如果没有“多态”能力,序列化并不知道该怎么转成 JSON,更不知道怎么解析回来。
Kotlin 的 sealed class = “受控的多态”
sealed class = 父类 + 多个明确的子类(受限制)
好处:编译器能知道所有子类型 → 更安全也更容易序列化。
图理解👇:
Message ← Sealed Class/ \TextMessage ImageMessage
Kotlinx.serialization 最爽的地方:天然支持 sealed class
只要用 @Serializable 标记 sealed class + 子类即可。
📌 注意:每个子类都要加
@Serializable。
@Serializable
sealed class Message {@Serializable@SerialName("text")data class TextMessage(val content: String) : Message()@Serializable@SerialName("image")data class ImageMessage(val url: String) : Message()
}
序列化 —— 用 Json 编码成字符串
val json = Json {classDiscriminator = "type" // JSON 的类型字段名
}val messages: List<Message> = listOf(Message.TextMessage("你好"),Message.ImageMessage("xx.png")
)val jsonStr = json.encodeToString(messages)
println(jsonStr)
输出 JSON:
[{"type": "text","content": "你好"},{"type": "image","url": "xx.png"}
]
你看到
"type": "text"和"type": "image"就是用于表示它是哪种子类。
反序列化 —— Json 字符串解析回对象
val backToObject = json.decodeFromString<List<Message>>(jsonStr)
println(backToObject)
解析结果:
[TextMessage(content=你好),ImageMessage(url=xx.png)
]
就是这么丝滑,完全不用自己判断类型!
对比传统方案(比如 Gson/Moshi)
传统做法:
需要写 Adapter/Factory
手动注册每个子类
JSON → 手动判断
"type"
👉 Kotlinx.serialization:一行都不用写,自动处理。
传统方案(示例)
场景设定
有一个父类型:
Animal
有两个子类:Cat、Dog
后端返回的 JSON 大概是这样:
{"type":"cat","name":"Tom","lives":9}
{"type":"dog","name":"Spike","hasBone":true}
1. 定义父类和子类
open class Animaldata class Cat(val name: String,val lives: Int
) : Animal()data class Dog(val name: String,val hasBone: Boolean
) : Animal()
2. 写一个“适配器”(JsonDeserializer)
这个适配器负责:
先读出
"type"字段根据值是
"cat"还是"dog"再交给 Gson 去反序列化成具体子类
import com.google.gson.*
import java.lang.reflect.Typeclass AnimalDeserializer : JsonDeserializer<Animal> {override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Animal {val obj = json.asJsonObjectval type = obj.get("type")?.asString?: throw JsonParseException("missing type")return when (type) {"cat" -> context.deserialize<Cat>(obj, Cat::class.java)"dog" -> context.deserialize<Dog>(obj, Dog::class.java)else -> throw JsonParseException("unknown type: $type")}}
}
核心就是
when (type)那几行:手动判断 "type" → 手动决定用哪个子类去解析。
3. 在 Gson 里注册这个适配器
val gson = GsonBuilder().registerTypeAdapter(Animal::class.java, AnimalDeserializer()).create()
4. 使用:把 JSON 转成 Animal / List<Animal>
4.1 单个对象
val jsonCat = """{"type":"cat","name":"Tom","lives":9}"""
val cat: Animal = gson.fromJson(jsonCat, Animal::class.java)
// 实际类型是 Cat
println(cat) // 输出:Cat(name=Tom, lives=9)
4.2 List<Animal>(要用 TypeToken)
val jsonList = """
[{"type":"cat","name":"Tom","lives":9},{"type":"dog","name":"Spike","hasBone":true}
]
""".trimIndent()val listType = object : com.google.gson.reflect.TypeToken<List<Animal>>() {}.type
val animals: List<Animal> = gson.fromJson(jsonList, listType)println(animals)
// [Cat(name=Tom, lives=9), Dog(name=Spike, hasBone=true)]
对比:
传统 Gson 多态:
JSON 里要有个
"type"字段(你自己约定)写一个
JsonDeserializer<父类>/TypeAdapter<父类>在里面:
读
"type"
when (type)分支判断
context.deserialize(...)解析成对应的子类
GsonBuilder.registerTypeAdapter(父类::class.java, 你的Adapter)现在用 Kotlinx.serialization:
sealed class +
@Serializable
Json { classDiscriminator = "type" }子类加
@SerialName("cat")/@SerialName("dog")根本不用自己写 “Adapter/Factory + when 判断” 那一坨。
sealed class 带来的额外好处
编译器会强制你处理所有子类(更安全)
IDE 自动提示
when不漏 case
例如:
when (msg) {is Message.TextMessage -> println("文本: " + msg.content)is Message.ImageMessage -> println("图片: " + msg.url)// 如果你忘记某个 case,会编译错误
}
总结(懂 sealed class = 懂多态序列化)
| 项目 | 含义 |
|---|---|
| sealed class | 定义父类 + 所有子类 |
| 多态 | 不同子类共享同一父类型 |
| Kotlinx.serialization 支持 sealed class | 自动识别类型,自动序列化 |
一句话:sealed class + Kotlinx.serialization = 多态序列化无痛方案
