前语

本文首发于,有意向转发的同行可私信,想在Android范畴有所前进的同伴,可微信搜索个人的公众号「Android技能集中营」或扫码增加,有不守时福利等大家来拿。

Android进阶宝典 -- 还在运用Gson吗?试下kotlin-serialization的强壮功用

1 传统序列化计划

现在在实际的事务开发中,服务端一般界说的数据结构为json数据,端上接收到服务端响应数据后,经过解析json生成对应的java bean类,做相应的页面数据出现,咱们在运用OkHttp和Retrofit网络库的时分,其实也是经过增加转化工厂GsonConvertFactory来完成。

这是传统的,也是很经典的数据转化形式,可是许多时分,服务端传递过来的数据咱们并不必定如咱们愿,看个场景:

val jsonStr = "{n" +
        ""name":"小明",n" +
        ""age":12,n" +
        ""sex":"",n" +
        ""school":"试验小学"n" +
        "}"
val person = Gson().fromJson(jsonStr, Person::class.java)
Log.d("TAG", "onCreate: $person")

假设服务端回来的数据正常,那么咱们在反序列化之后,能够安全地运用每个字段数据进行UI的展现,那么假设服务端回来的数据反常,例如school字段没有回来值,或者字段缺失,那么就会出现下面这种状况。

 val jsonStr = "{n" +
                ""name":"小明",n" +
                ""age":12,n" +
                ""sex":""" +
                "}"
val person = Gson().fromJson(jsonStr, Person::class.java)
Log.d("TAG", "onCreate: $person")
终究序列化的成果:Person(name=小明, age=12, sex=男, school=null)

这个时分,咱们取school字段的数据,拿到的便是null,此刻就会抛出空指针反常,因此咱们在运用Kotlin的时分,通常会在实体类中对某些字段加空安全的处理,例如:

data class Person(
    val name: String,
    val age: Int,
    val sex: String,
    val school: String?
)

此刻在运用school这个字段的时分,就会提示此字段可能为空,需求做判空处理。一些同伴想:能不能加默许值,其实是没用的,当json数据被反序列化之后,默许值也将被掩盖。

所以针对传统Gson存在的问题,自然有计划去处理这个问题,在Android进阶宝典 — App线上网络问题优化策略这篇文章中,我介绍过如何在GsonConvertFactory中做类型的适配,可是假设针对每个数据类型都做一层适配,明显不太适宜,所以Kotlin官网推出了针对Kotlin序列化的东西kotlin-serialization,看它具有的优势有哪些。

2 kotlin-serialization

kotlin-serialization是Kotlin供给的支撑序列化的东西,支撑JSON、Protobuf等常见的数据结构,旨在处理Gson在Kotlin中运用的限制。

2.1 基本运用

在Android项目中运用kotlin-serialization,首要需求引进依靠:

implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1")

此刻假设想要给一个类赋予序列化的能力,需求运用@Serializable注解,

@Serializable
data class Person(
    val name: String,
    val age: Int,
    val sex: String,
    val school: String
)

到这儿并没有结束,此刻编辑器对此注解曝黄,并提示:

kotlinx.serialization compiler plugin is not applied to the module, so this annotation would not be processed. Make sure that you’ve setup your buildscript correctly and re-import project.

需求咱们在当前项目中引进插件,不然此注解将不会收效。

  1. 首要在project级别的gradle文件下,配置classpath
plugins{
    id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.10'
}
  1. 在app级别的gradle文件中,引进serialization插件
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'org.jetbrains.kotlin.plugin.serialization'
}

此刻能够经过Json的扩展办法decodeFromString完成对json数据的反序列化。

val jsonStr = "{n" +
        ""name":"小明",n" +
        ""age":12,n" +
        ""sex":""" +
        ",n" +
        ""school":"试验小学"n" +
        "}"
val person = Json.decodeFromString<Person>(jsonStr)
//序列化
val personStr = Json.encodeToString(person)

假设想要对一个对象序列化,则能够运用encodeToString办法完成,可是需求记住运用kotlin-serialization的前提是需求运用@Serializable注解润饰实体类,不然会直接抛出反常。

2.2 只要backing fields支撑序列化

什么是backing fields,其实许多同伴看到之后也是一脸懵。其实这是Kotlin中特有的一个特点,例如咱们自界说一个变量,那么Kotlin默许给生成了get/set办法。

@Serializable
data class Person(
    val name: String,
    val age: Int,
    val sex: String,
    val school: String
) {
    val teacher: String = "李老师"
    var classmate: String = "小张"
        get() = field
        set(value) {
            field = value
        }
}

例如classmate特点,Kotlin默许生成了get/set办法,其间get办法获取到的便是field,其实便是classmate的值,而set办法则是给filed赋值,所以只要是有backing filed的变量均可被序列化。

而像teacher特点,只要get办法,不能被赋值,此类特点不可被序列化,从终究的JSON数据中看到是没有teacher这个字段的。

val person1 = Person("小明",12,"男","试验中学").apply {
    classmate = "Alice"
}
val personStr = Json.encodeToString(person1)
Log.d("TAG", "onCreate: $personStr")
//输出json数据:
{"name":"小明","age":12,"sex":"男","school":"试验中学","classmate":"Alice"}

2.3 数据验证

例如咱们从服务拿到用户的数据,其间name字段是必须要有的,假设没有那么后续的事务流程无法履行,因此在实体类中,能够进行数据的强校验,例如:

@Serializable
data class Person(
    val name: String,
    val age: Int,
    val sex: String,
    val school: String
) {
    init {
        require(name.isEmpty()) {
            "name 不能为空"
        }
    }
}

假设此刻回来的name数据为空字符串,那么在反序列的时分就会直接抛出反常,此刻事务上层可直接根据抛出的反常进行反常处理。

2.4 数据解析空安全

回到文章开头的case,当从服务端回来的json数据中,缺失了要害字段之后,运用Gson解析会导致当前缺失字段为null,即便是设置了初始值,也依然存在空指针的危险,那么在运用kotlin-serialization时,咱们是有办法规避这个危险的。

val jsonStr = "{n" +
                ""name":"小明",n" +
                ""age":12,n" +
                ""sex":""" +
                "}"
val person = Json.decodeFromString<Person>(jsonStr)
Log.d("TAG", "onCreate: ${person.school}")

明显,json数据中缺失了school字段,那么直接取反序列化数据会直接抛反常:

kotlinx.serialization.MissingFieldException: Field 'school' is required for type with serial name 'com.example.nowinandroid.data.Person', but it was missing

那么处理这个问题的计划便是,给school特点赋初始值。

@Serializable
data class Person(
    val name: String,
    val age: Int,
    val sex: String,
    val school: String = "试验中学"
) 

此刻咱们拿到的school值便是默许值试验中学,这里与Gson不同的便是,kotlin-serialization在反序列化之后,假设原始数据字段缺失,不会掩盖实体类的初始值。

这只是一种状况,假设school字段服务端回来了,可是值为null,此刻依然仍是会有空指针的危险,那么此刻需求运用默许值,在Json结构函数中,开启coerceInputValues,假设school值为空,那么仍是会取默许值。

val jsonStr = "{n" +
        ""name":"小明",n" +
        ""age":12,n" +
        ""sex":""" +
        ",n" +
        ""school":nulln" +
        "}"
val jsonClient = Json {
    coerceInputValues = true
}
val person = jsonClient.decodeFromString<Person>(jsonStr)

所以相较于在实体类中设置空安全,导致事务逻辑中一堆?,这种方法明显更友好一些。

2.5 Transient特点排除

假设实体类中,某个特点不需求参加序列化,能够给其赋默许值,所以咱们需求记住一点,在data class中界说的特点是默许支撑序列化的,假设不想参加序列化,一种方法便是设置默许值。

@Serializable
data class Person(
    val name: String,
    val age: Int,
    val sex: String,
    val school: String = "试验中学"
) {
    val teacher: String = "李老师"
    var classmate: String = "小张"
        get() = field
        set(value) {
            field = value
        }
}

school界说了默许值,那么就会参加序列化,假设是普通class中的特点,例如classmate不想参加序列化,那么就能够运用@Transient注解润饰。

@Transient
var classmate: String = "小张"
    get() = field
    set(value) {
        field = value
    }

在运用@Transient注解润饰特点时,该特点必须要有一个初始值。

2.6 默许值参数不参加序列化

在2.5 小节中,咱们说到了假设一个特点有默许值,那么是不会参加序列化的,那么假设要默许值也参加序列化,那么能够运用@EncodeDefault注解润饰。

2.7 引证对象类型

假设在一个类中,引证了另外一个类,这个类想要参加序列化,那么也需求运用@Serializable注解润饰。

@Serializable
data class Person(
    val name: String,
    val age: Int,
    val sex: String,
    val school: String = "试验中学",
    val family: Family
) {
    val teacher: String = "李老师"
    @Transient
    var classmate: String = "小张"
        get() = field
        set(value) {
            field = value
        }
}
@Serializable
data class Family(
    val parentName:String
)

这个规矩适用于任何一个序列化/反序列化结构

2.8 泛型类的支撑

现在有一个泛型类ClassA,经过@Serializable润饰支撑序列化,另外一个类ClassContainer,支撑两种泛型类。

@Serializable
data class ClassA<T>(val t: T)
@Serializable
data class ClassContainer(
    val member: ClassA<Int>,
    val member2: ClassA<String>
)

那么经过序列化,咱们能够看到json数据中展现了两种不同的数据类型。

val classContainer = ClassContainer(
    ClassA(1), ClassA("泛型支撑")
)
val str = Json.encodeToString(classContainer)
//成果:
{"member":{"t":1},"member2":{"t":"泛型支撑"}}

当然,像Gson一样,kotlin-serialization相同也支撑在Retrofit中运用,能够运用kotlin-serialization自带的数据转化工厂替换GsonConvertFactory,那么后续的数据转化要完全运用kotlin-serialization的规矩,这势必会带来必定的学习成本,总之没有最好的,只要适宜的才算最好的,能够视状况挑选适宜的东西。