Serialization是JetBrains开源的解析库, 具备多平台和支持多解析格式, 也是Kotlin上最强大的序列化解析库没有之一
文档:
Serialization独有优势
- 非空校验/非空覆盖, 不会覆盖构造参数默认值, 解决后端返回null覆盖字段问题
- 解决泛型擦除问题, 直接序列化/反序列化List/Map/Pair等
- 非反射高性能
- 支持多种格式(ProtoBuf/CBOR/自定义)
- 注解数据类自动生成序列者, 手动构造序列者
- 代码简洁优雅
- 动态解析
Serialization由于其使用Kotlin的KType而非Java, 这里推荐使用Net网络请求库中的转换器. 可以实现指定任何泛型解析结果: 接入文档.
使用Net可以编写最优雅的请求代码
安装
项目 build.gradle
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
// 和Kotlin插件同一个版本号即可
module build.gradle
apply plugin: 'kotlinx-serialization'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0"
JSON使用
关键类 | |
---|---|
Json | 单例对象, 用于解析JSON的配置 |
Json.Default | 默认配置的单例对象 |
函数
Json的函数分为内联泛型函数/普通泛型函数.
- 内联泛型函数具备自动解析对象类型. 一般情况直接使用该函数即可
- 普通泛型要求指定解析器. 应对于解析封装
函数 | 描述 |
---|---|
encodeToString | 序列化到字符串 |
encodeToJsonElement | 序列化到JsonElement |
decodeFromString | 反序列化自字符串 |
decodeFromJsonElement | 反序列化自JsonElement |
示例
// 序列化
val json:String = Json.encodeToString(Model("彭于晏", 23))
// 反序列化
val json = """{"name":"彭于晏","age":33}"""
val model = Json.decodeFromString<Model>(json)
序列化
- 仅属性支持序列化(即包含setter和getter)
- 默认值不会被序列化, 即使是可空属性
@Serializable
class Project(val name: String, val language: String)
fun main() {
val data = Project("kotlinx.serialization", "Kotlin")
println(Json.encodeToString(data))
}
反序列化
@Serializable
class Project(val name: String, val language: String)
fun main() {
val data = Project("kotlinx.serialization", "Kotlin")
println(Json.encodeToString(data))
}
- 私有构造函数, 暴露其他构造函数, 这样Serialization会序列化暴露出来的构造函数属性
- 当数据类字段比JSON多出属性会序列化失败, 除非多出的属性存在默认值(
@Required
则强制要求JSON匹配字段) - 序列化时如果存在循环结构字段, 会导致堆栈溢出, 建议你忽略排除该字段
- 当JSON字段覆盖属性值时, 属性值的默认值为一个函数, 该函数不会被调用
- JSON覆盖存在默认值的属性会错误
注解
注解 | 修饰位置 | 描述 |
---|---|---|
@Transient | 字段 | 忽略字段 |
@Required | 字段 | 强制有默认值的参数也要匹配JSON字段 |
@SerialName | 字段 | 修饰类为指定序列者的名称, 修饰字段为指定在JSON中的名称 |
@JsonNames | 字段 | 可以为字段再指定多个名称, 同时字段名也不会失效 |
@SerialInfo | 类 | 允许编译器将注释信息保存到SerialDescriptor中, 使用getElementAnnotations |
@Serializer | 类 | 其指定参数为目标类, 目标类以其修饰类创建序列化器 |
模型
- 暂时不支持无符号类型/内联类
- 单例对象不支持被序列化(Unit属于单例), 序列化结果为
{}
- 父类的属性不会被序列化
- 对象只有赋值才会被序列化(赋值null也会参与), 存在默认值也不会(除非使用@Required修饰).
- 委托字段/Val字段都不会参与序列化. 只有同时拥有set/get才会被序列化(构造参数允许val)
- 私有构造函数的字段也会被序列化, 如果构造参数非val或者var也不会参与序列化
@Serializable
class Model(var name: String, var age: Int) {
var height: Long? = 23 // 不赋值绝对不会被序列化
}
或者直接在文件中声明序列者
@file:UseSerializers(DateAsLongSerializer::class)
自定义序列者, BoxSerializer
既自定义的序列化器
@Serializable(with = BoxSerializer::class)
data class Box<T>(val contents: T)
当类无法被修饰时我们可以为序列器使用修饰符@Serializer
@Serializer(forClass = Project::class)
object ProjectSerializer
不仅仅是构造参数才会被序列, 只有拥有getter/setter函数的属性都会参与
父类被@Serializable修饰, 其子类也必须被修饰
@Serializable
open class Project(val name: String)
class OwnedProject(name: String, val owner: String) : Project(name)
fun main() {
val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")
println(Json.encodeToString(data))
val data = OwnedProject("kotlinx.coroutines", "kotlin") // 抛出异常
}
仅密封类允许抽象属性, 如果不是密封类则不允许
@Serializable
sealed class Project {
abstract val name: String
}
@Serializable
class OwnedProject(override val name: String, val owner: String) : Project()
fun main() {
val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")
println(Json.encodeToString(data))
}
type
键的值可以使用属性名
@Serializable
@SerialName("owned")
class OwnedProject(override val name: String, val owner: String) : Project()
当集合存在多态时会默认添加一个type
字段
插件生成
一般情况下推荐使用插件JSON to Kotlin Class
插件生成数据模型. 并且生成带有默认值的数据类, 可以保证后端字段null情况不会覆盖默认值导致空指针
-
@Serializable data class Data(var name:String = "", var age:Int = 0)
-
val jsonDecoder = Json { ignoreUnknownKeys = true // JSON和数据模型字段可以不匹配 coerceInputValues = true // 如果JSON字段是Null则使用默认值 }
Json配置
使用Json{}
构建Json实例对象
val format = Json { prettyPrint = true }
@Serializable
data class Project(val name: String, val language: String)
fun main() {
val data = Project("kotlinx.serialization", "Kotlin")
println(format.encodeToString(data))
}
选项 | 描述 | 默认值 |
---|---|---|
prettyPrint: Boolean | 生成排版后的JSON | false |
prettyPrintIndent: String | 指定排版的缩进字符串 | false |
isLenient: Boolean | 允许非双引号包裹键值 (推荐配置) | false |
ignoreUnknownKeys: Boolean | 允许反序列化的数据类缺失字段 (推荐配置) | false |
explicitNulls: Boolean | 反序列化时json不存在的值赋值给数据类null, 序列化时不存在且无默认值的赋值null | false |
useAlternativeNames: Boolean | 是否启用@JsonName注解, 如果不用该注解建议禁用该配置提高性能 | true |
coerceInputValues: Boolean | 空和未知枚举不会覆盖属性默认值 | false |
allowStructuredMapKeys: Boolean | 启用Map序列化. 默认情况是不能序列化Map | false |
allowSpecialFloatingPointValues: Boolean | 允许序列化Double.NaN 这种特殊浮点类型 | false |
classDiscriminator = "#class" | 使用属性名作为值, #class 为自定义ide键名 | |
serializersModule = moduleForDate | ||
encodeDefaults: Boolean | 是否序列化默认值 支持父类的属性(非抽象)序列化 | false |
启用Map序列化(默认情况是不能序列化Map)
val format = Json { allowStructuredMapKeys = true }
@Serializable
data class Project(val name: String)
fun main() {
val map = mapOf(
Project("kotlinx.serialization") to "Serialization",
Project("kotlinx.coroutines") to "Coroutines"
)
println(format.encodeToString(map))
}
类描述
这样在序列化的JSON中会自动新增一个字段用于描述数据模型的类信息
val format = Json { classDiscriminator = "#class" } // key
@Serializable
sealed class Project {
abstract val name: String
}
@Serializable
@SerialName("owned") // value
class OwnedProject(override val name: String, val owner: String) : Project()
fun main() {
val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")
println(format.encodeToString(data))
// {"#class":"owned","name":"kotlinx.coroutines","owner":"kotlin"}
}
允许规定浮点值
val data = Data(Double.NaN)
println(format.encodeToString(data))
// {"value":NaN}
启用默认值
设置coerceInputValues
为true, 会在类型为不可空, 但是JSON值为空的情况下采用默认值而非覆盖字段.
同时当出现未知的枚举类型也会使用默认值
字段缺失
字段缺失分为两种
- 数据类字段比json中少, 使用
ignoreUnknownKeys = true
- json字段比数据类少
- 当数据类字段为可空, 则赋值为null. 要求
explicitNulls = false
- 当数据类型字段存在默认值(无论是否为可空), 则使用默认值
- 当数据类字段为可空, 则赋值为null. 要求
配置示例
val jsonDecoder = Json {
ignoreUnknownKeys = true
explicitNulls = false
}
JsonElement
使用函数 Json.parseToJsonElement
解析出一个JsonElement对象, 该对象非反序列化
子类 | 描述 |
---|---|
JsonPrimitive | Kotlin中的原始类型 |
JsonArray | 一个JsonElement的集合 |
JsonObject | 一个JsonElement的Map集合 |
JsonNull | 空类型 |
JsonPrimitive
具备一系列基础类型获取函数, 返回String请调用content
函数.
函数 | 描述 |
---|---|
jsonPrimitive | 返回原始类型 |
jsonObject | 返回Map |
jsonArray | 返回集合 |
jsonNull | 返回Null |
fun main() {
val element = Json.parseToJsonElement("""
{
"name": "kotlinx.serialization",
"forks": [{"votes": 42}, {"votes": 9000}, {}]
}
""")
val sum = element
.jsonObject["forks"]!!
.jsonArray.sumOf { it.jsonObject["votes"]?.jsonPrimitive?.int ?: 0 }
println(sum)
}
构建JSON
提供顶层DSL函数构建JSON
fun main() {
val element = buildJsonObject {
put("name", "kotlinx.serialization")
putJsonObject("owner") {
put("name", "kotlin")
}
putJsonArray("forks") {
addJsonObject {
put("votes", 42)
}
addJsonObject {
put("votes", 9000)
}
}
}
println(element)
}
KSerializer
KSerializer属于Serialization中的序列者, 一个包含序列化和反序列化的接口规范.
可以通过以下方式创建序列器
-
绑定数据类
@Serializable(with = ColorAsStringSerializer::class) class Color(val rgb: Int)
-
在序列器上指定对象
// NOT @Serializable class Project(val name: String, val language: String) @Serializer(forClass = Project::class) object ProjectSerializer // 该序列器没有任何代码逻辑就可以使用
-
函数参数
public fun <T> decodeFromJsonElement(deserializer: DeserializationStrategy<T>, element: JsonElement): T
-
指定当前文件的类使用的序列器
@file:UseSerializers(DateAsLongSerializer::class) @Serializable class ProgrammingLanguage(val name: String, val stableReleaseDate: Date)
自动生成序列器
每个被@Serializable
修饰的类都存在一个单例函数serializer()
返回一个KSerializer<T>
序列化
- 故你不能声明创建一个
serializer
函数 - 原始类型存在默认的序列者:
Int.serializer()
// 序列化
public interface SerializationStrategy<in T> {
public val descriptor: SerialDescriptor
public fun serialize(encoder: Encoder, value: T)
}
// 反序列化
public interface DeserializationStrategy<T> {
public val descriptor: SerialDescriptor
public fun deserialize(decoder: Decoder): T
}
// 序列者
public interface KSerializer<T> : SerializationStrategy<T>, DeserializationStrategy<T> {
override val descriptor: SerialDescriptor
}
编码和解码
编码和解码 | 描述 |
---|---|
Encoder | 序列化根接口 |
CompositeEncoder | |
JsonEncoder | 用于JSON序列化 |
Decoder | 反序列化根接口 |
CompositeDecoder | |
JsonDecoder | 用于JSON反序列化 |
Encoder可以使用encodeStructure
来开始手动编码
Decoder内部存在一个循环一直调用decodeElementIndex
开始解码, 遇到CompositeDecoder.DECODE_DONE
时停止循环
如果数据是按照顺序存储的, 我们可以直接使用decodeSequentially
函数来终止循环
override fun deserialize(decoder: Decoder): Color =
decoder.decodeStructure(descriptor) {
var r = -1
var g = -1
var b = -1
if (decodeSequentially()) { // sequential decoding protocol
r = decodeIntElement(descriptor, 0)
g = decodeIntElement(descriptor, 1)
b = decodeIntElement(descriptor, 2)
} else while (true) {
when (val index = decodeElementIndex(descriptor)) {
0 -> r = decodeIntElement(descriptor, 0)
1 -> g = decodeIntElement(descriptor, 1)
2 -> b = decodeIntElement(descriptor, 2)
CompositeDecoder.DECODE_DONE -> break
else -> error("Unexpected index: $index")
}
}
require(r in 0..255 && g in 0..255 && b in 0..255)
Color((r shl 16) or (g shl 8) or b)
}
类型
Serialization中定义了很多顶层函数来创建序列者: BuiltinSerializers.kt
序列者 |
---|
PairSerializer |
MapEntrySerializer |
TripleSerializer |
*ArraySerializer |
ListSerializer |
SetSerializer |
MapSerializer |
LongAsStringSerializer |
基本囊括了所有数据类型, 反序列化不需要使用序列者默认支持类型. 枚举序列化和反序列化都不需要多余的处理
使用泛型类型推断可以快速生成对应的序列者
val stringToColorMapSerializer: KSerializer<Map<String, Color>> = serializer()
println(stringToColorMapSerializer.descriptor)
Serialization解析Map, 键永远是字符串, 即使是数字
@Serializable
class Project(val name: String)
fun main() {
val map = mapOf(
1 to Project("kotlinx.serialization"),
2 to Project("kotlinx.coroutines")
)
println(Json.encodeToString(map))
}
泛型
@Serializable
class Box<T>(val contents: T)
fun main() {
val boxedColorSerializer = Box.serializer(Color.serializer())
println(boxedColorSerializer.descriptor)
}
- 泛型数量会导致要求传递给
serializer
的参数数量, 每个泛型参数都应该有属于自己的序列者
Json序列化
JsonTransformingSerializer
属于Json解析实现. 如果我们需要自定义解析JSON的序列化器我们可以继承该类实现函数
public abstract class JsonTransformingSerializer<T : Any>(
private val tSerializer: KSerializer<T>
) : KSerializer<T> {
// 反序列化
protected open fun transformDeserialize(element: JsonElement): JsonElement = element
// 序列化
protected open fun transformSerialize(element: JsonElement): JsonElement = element
}
过滤掉某值(默认情况默认值会被过滤)
为了方便使用和节约内存, 解析器一般使用单例对象
fun main() {
val data = Project("kotlinx.serialization", "Kotlin")
println(Json.encodeToString(data)) // using plugin-generated serializer
println(Json.encodeToString(ProjectSerializer, data)) // using custom serializer
}
@Serializable
class Project(val name: String, val language: String)
object ProjectSerializer : JsonTransformingSerializer<Project>(Project.serializer()) {
override fun transformSerialize(element: JsonElement): JsonElement =
// 如果键 "language" 的值为 "Kotlin" 则过滤掉
JsonObject(element.jsonObject.filterNot {
(k, v) -> k == "language" && v.jsonPrimitive.content == "Kotlin"
})
}
多态序列化提供一个函数用于返回具体序列化器
object ProjectSerializer : JsonContentPolymorphicSerializer<Project>(Project::class) {
override fun selectDeserializer(element: JsonElement) = when {
"owner" in element.jsonObject -> OwnedProject.serializer()
else -> BasicProject.serializer()
}
}
ProtoBuf
protobuf属于比json体积/性能更好的数据格式, 应用同样也比较广泛. 这里介绍如何通过ks快速处理protobuf
implementation "org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.3.0"
示例
@Serializable
data class Project(val name: String, val language: String)
fun main() {
val data = Project("kotlinx.serialization", "Kotlin")
val bytes = ProtoBuf.encodeToByteArray(data)
println(bytes.toAsciiHexString())
val obj = ProtoBuf.decodeFromByteArray<Project>(bytes)
println(obj)
}
字段编号
使用注解@ProtoNumber
来表示
@Serializable
data class Project(
@ProtoNumber(1)
val name: String,
@ProtoNumber(3)
val language: String
)
fun main() {
val data = Project("kotlinx.serialization", "Kotlin")
val bytes = ProtoBuf.encodeToByteArray(data)
println(bytes.toAsciiHexString())
val obj = ProtoBuf.decodeFromByteArray<Project>(bytes)
println(obj)
}
Int类型
@Serializable
class Data(
@ProtoType(ProtoIntegerType.DEFAULT)
val a: Int,
@ProtoType(ProtoIntegerType.SIGNED)
val b: Int,
@ProtoType(ProtoIntegerType.FIXED)
val c: Int
)
fun main() {
val data = Data(1, -2, 3)
println(ProtoBuf.encodeToByteArray(data).toAsciiHexString())
}
- ProtoIntegerType.DEFAULT 使用protobuf中的
int*
类型, 针对正数优化 - ProtoIntegerType.SIGNED 使用protobuf中的
sint*
类型, 针对负数优化 - ProtoIntegerType.FIXED 使用protobuf中的
fixed*
类型, 固定字节
uintXX
和sfixedXX
类型暂时不支持