Kotlinx.serialization 深度解析:值类的序列化处理
什么是值类?
值类(Value Class)是 Kotlin 中一种特殊的类,通过 @JvmInline
注解标记,主要用于包装单个值而不引入运行时开销。值类在可能的情况下会直接存储为其底层类型(不需要装箱操作),这为性能优化提供了可能。
值类的序列化基础
在 kotlinx.serialization 中处理值类非常简单,只需为值类添加 @Serializable
注解:
@Serializable
@JvmInline
value class Color(val rgb: Int)
当值类被序列化时,框架会直接使用其底层类型,不会包含值类本身的类型信息。例如:
@Serializable
data class NamedColor(val color: Color, val name: String)
fun main() {
println(Json.encodeToString(NamedColor(Color(0), "black")))
}
输出结果为:
{"color": 0, "name": "black"}
可以看到,输出中只包含底层 Int
值,没有 Color
类型信息。
集合中的值类处理
即使值类被用作泛型类型参数(此时会触发实际分配),序列化行为依然保持一致:
@Serializable
class Palette(val colors: List<Color>)
fun main() {
println(Json.encodeToString(Palette(listOf(Color(0), Color(255), Color(128)))))
}
输出:
{"colors":[0, 255, 128]}
JSON 对无符号类型的特殊支持
Kotlin 标准库通过值类提供了无符号类型:UByte
、UShort
、UInt
和 ULong
。kotlinx.serialization 的 JSON 格式为这些类型提供了内置支持,将它们序列化为无符号形式的字符串表示。
@Serializable
class Counter(val counted: UByte, val description: String)
fun main() {
val counted = 239.toUByte()
println(Json.encodeToString(Counter(counted, "tries")))
}
输出:
{"counted":239,"description":"tries"}
注意:目前只有 JSON 格式支持无符号类型的这种特殊表示,其他格式如 ProtoBuf 和 CBOR 会使用底层的有符号表示。
为值类编写自定义序列化器
当需要为包含值类的类编写自定义序列化器时,需要注意避免值类的装箱操作。常规的 encodeSerializableElement
会导致值类被装箱,应该改用 encodeInlineElement
:
override fun serialize(encoder: Encoder, value: NamedColor) {
encoder.encodeStructure(descriptor) {
encodeInlineElement(descriptor, 0).encodeInt(value.color)
encodeStringElement(descriptor, 1, value.name)
}
}
encodeInlineElement
返回一个 Encoder
,可以直接对未装箱的值进行操作。
将类表示为原始值
如果类需要表示为原始值(而非结构),可以使用 encodeInline
方法。例如,将一个包含 Int
的类表示为 JSON 中的无符号整数:
@Serializable(UIDSerializer::class)
class UID(val uid: Int)
object UIDSerializer: KSerializer<UID> {
override val descriptor = UInt.serializer().descriptor
override fun serialize(encoder: Encoder, value: UID) {
encoder.encodeInline(descriptor).encodeInt(value.uid)
}
override fun deserialize(decoder: Decoder): UID {
return UID(decoder.decodeInline(descriptor).decodeInt())
}
}
这里的关键是使用了 UInt
的描述符,使得 JSON 格式器将后续的 encodeInt
调用处理为无符号整数。
重要注意事项
- 为值类编写自定义序列化器时,应该只使用
encodeInline
相关方法 - 值类序列化器的调用可能会被优化替换为
encodeInlineElement
调用 - 相同的描述符应该总是产生相同的
Encoder
行为 - 避免在值类序列化器中嵌入可能被优化掉的逻辑
通过合理利用值类的序列化特性,可以在保持类型安全的同时获得最佳的性能表现。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考