Kotlin最强Json/Protobuf解析框架 - kotlin-serialization

Serialization是JetBrains开源的解析库, 具备多平台和支持多解析格式, 也是Kotlin上最强大的序列化解析库没有之一

文档:

Serialization独有优势

  • 非空校验/非空覆盖, 不会覆盖构造参数默认值, 解决后端返回null覆盖字段问题
  • 解决泛型擦除问题, 直接序列化/反序列化List/Map/Pair等
  • 非反射高性能
  • 支持多种格式(ProtoBuf/CBOR/自定义)
  • 注解数据类自动生成序列者, 手动构造序列者
  • 代码简洁优雅
  • 动态解析

Serialization由于其使用Kotlin的KType而非Java, 这里推荐使用Net网络请求库中的转换器. 可以实现指定任何泛型解析结果: 接入文档.

使用Net可以编写最优雅的请求代码

image-20210520190454028

安装

项目 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)
    复制代码
  • 推荐Json配置

    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
    • 当数据类型字段存在默认值(无论是否为可空), 则使用默认值

配置示例

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中的序列者, 一个包含序列化和反序列化的接口规范.

可以通过以下方式创建序列器

  1. 绑定数据类

    @Serializable(with = ColorAsStringSerializer::class)
    class Color(val rgb: Int)
    复制代码
  2. 在序列器上指定对象

    // NOT @Serializable
    class Project(val name: String, val language: String)
    
    @Serializer(forClass = Project::class)
    object ProjectSerializer
    // 该序列器没有任何代码逻辑就可以使用
    复制代码
  3. 函数参数

    public fun <T> decodeFromJsonElement(deserializer: DeserializationStrategy<T>, element: JsonElement): T
    复制代码
  4. 指定当前文件的类使用的序列器

    @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*类型, 固定字节

uintXXsfixedXX 类型暂时不支持

猜你喜欢

转载自juejin.im/post/6963676982651387935
今日推荐