kotlin序列化库kotlinx-serialization第一部分(基本使用)

6,786 阅读7分钟

导入

implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2"

基本序列化

@Serializable
class Project(val name: String, val language: String)

fun main() {
    val data = Project("kotlinx.serialization", "Kotlin")
    println(Json.encodeToString(data))
}

基本反序列化

@Serializable
data class Project(val name: String, val language: String)

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization","language":"Kotlin"}
    """)
    println(data)
}

不存是序列化还是反序列化,所有参与的类要求必须使用@Serializable注解

序列化字段

只有支持字段才可以被序列化,比如data class中定义的属性或者普通class中定义的有默认值字段。没有提供getter、setter的属性以及委托属性不会被序列化,程序运行过程中不会出错,只是不会输入没有被序列化的字段。

@Serializable
class Project(
    // 默认
    var name: String
) {
    var stars: Int = 0 // 默认提供了get set,所以可以被序列化

    val path: String // 只有get,没有提供set,不会被序列化
        get() = "kotlin"


    var id by ::name // 委托属性不会被序列化
}

构造函数的要求

被@Serializable注解的类要求其构造函数的参数必须是var或者val修饰的属性,所以下面的写法是错误的,IDE会有错误提示

@Serializable
class Project(name: String, val language: String)
//这种写法IDE会有错误提示 
//This class is not serializable automatically because it has primary constructor parameters that are not properties
fun main() {
    val data = Project("kotlinx.serialization", "Kotlin")
    println(Json.encodeToString(data))
}

我们可以为构造器中的name字段添加var或者val修饰,使其变为属性,也可以使用另一种写法

@Serializable
class Project private constructor(val owner: String, val name: String) {
    constructor(path: String) : this(
        owner = path.substringBefore('/'),
        name = path.substringAfter('/')
    )
    val path: String
        get() = "$owner/$name"
}

fun main() {
    println(Json.encodeToString(Project("kotlin/kotlinx.serialization")))
}

数据校验

我们可以对原始JSON字符串中的内容做一些校验,比如验证字段是否是null等等,这时候就需要在类中添加init{}初始化块并且在里边做非空校验。

@Serializable
class Project(var name:String) {
    init {
        require(name.isNotEmpty()) { "name cannot be empty" }
    }
}

fun main() {
    val data = Json.decodeFromString<Project>("""{"name":""}""")
    println(data)
}

运行上面的代码将会抛出一个异常:

Exception in thread "main" java.lang.IllegalArgumentException: name cannot be empty。

或者我们也可以做一些其他的处理,比如根据JSON中的name字段值是否为空判断是否需要为类中的name属性重新赋值:

@Serializable
class Project(var name:String) {
    init {
        if (name.isNullOrBlank()){
            name="kotlinx.serialization"
        }
    }
}

fun main() {
    val data = Json.decodeFromString<Project>("""{"name":""}""")
    println(data.name)
    //输出 kotlinx.serialization
}

可选属性

反序列化的时候,如果类中的属性个数多于原始JSON串中定义的字段个数,解析的过程中就会报错,提示字段丢失。

@Serializable
data class Project(val name: String, val language: String)

fun main() {
    val data = Json.decodeFromString<Project>("""{"name":"kotlinx.serialization"}""")
    println(data.language)
}
//解析失败 Exception in thread "main" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'example.exampleClasses04.Project', but it was missing

这种情况下可以为提示缺失的字段在类中添加默认值就可以成功解析

@Serializable
data class Project(val name: String, val language: String="kotlin")

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization"}
    """)
    println(data.language)
  // 输出kotlin
}

调用默认值

当满足以下条件时参与反序列化的类中的默认值将会被覆盖

  1. 原始JSON串和类中的字段数量以及名称相同
  2. 类中的一个或者多个属性存在默认值
@Serializable
data class Project(val name: String, val language: String ="Java")

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization","language":"kotlin"}
    """)
    println(data)
    //输出 Project(name=kotlinx.serialization, language=kotlin)
}

必要属性

当参与反序列化的类中某个字段被@Required注解时,说明这个属性在整个解析过程中是不可缺少的,这就要求对应的JSON字符串也必须要有相同名字相同类型的字段,不然会报错

@Serializable
data class Project(val name: String, @Required val language: String)
@Serializable
data class A(val name: String)

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization"}
    """)
    println(data)
}

执行这段代码会报错提示language字段是必需的,但是JSON串中并没有该字段

Exception in thread "main" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name

同样,如果JSON串提供了language字段,但是类型与Project类中要求的类型不一致,也会报错类型匹配错误

Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 53: Expected quotation mark '"', but had '(' instead

属性排除

如果想要类中的某一个属性不参与序列化过程,可以使用@Transient注解将该属性排除,这样这个属性就不会被序列化或者反序列化。

@Serializable
data class Project(val name: String, @Transient val language: String = "Kotlin")

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization","language":"Kotlin"}
    """)
    println(data)
}

执行这段代码会提示unknown key的错误

Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 42: Encountered an unknown key 'language'. Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys.

出现这个错误的原因就是使用了@Transient注解将language属性从需要序列化的属性列表中排除了,但是JSON串中依然有这个字段,所以就会报错。要规避这个异常只需要按照提示在Json中添加ignoreUnknownKeys = true即可,这样就可以忽略掉JSON串中多余的字段。

val jsonString="""{"name":"kotlinx.serialization","language":"Kotlin"}"""
val data = Json{ignoreUnknownKeys = true}.decodeFromString<Project>(jsonString)

启用默认值

默认情况下我们给类属性设置的默认值是不会参与序列化,比如下面的序列化代码

@Serializable
data class Project(val name: String, val language: String = "Java")

fun main() {
    val data = Project("kotlinx.serialization")
    println(Json.encodeToString(data))
}

运行过程也不会出错,但是最终的得到JSON串结果可能和我们想象的不一样,按道理我们在创建Project这个类的时候已经为language字段设置了默认值,虽然创建对象实例的时候只传了一个构造参数,那么最后序列化生成的字符串应该也会包含language字段的,但是实际结果却是并没有包含language字段。这里边的原因就是serialization中我们为属性设置的默认值默认是不启用的,所以最后序列话生成生成的内容也就没有对应的字段了。但是反序列化的时候默认值是生效的。

@Serializable
data class Project(val name: String, val language: String = "Java")

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization"}
        """)
    println(data)
    //输出 Project(name=kotlinx.serialization, language=Java)
}

要想在序列化时启用属性默认值,需要在对应的属性前面添加@EncodeDefault注解。

可空属性

如果类中的属性被标明为可空类型,这个属性也不会参与序列化,因为之前提到过,默认属性不会参与序列化,除非添加

@Serializable
class Project(val name: String, val renamedTo: String? = null)

fun main() {
    val data = Project("kotlinx.serialization")
    println(Json.encodeToString(data))
    //输出 {"name":"kotlinx.serialization"}
    //添加@EncodeDefault注解后输出{"name":"kotlinx.serialization","renamedTo":null}
}

类型安全

将JSON中的null解码为对象中的非空字段时候会报错

@Serializable
data class Project(val name: String, val language: String = "Kotlin")

fun main() {
    val data = Json.decodeFromString<Project>("""{"name":"kotlinx.serialization","language":null}""")
    println(data)
}

运行过程中会报错

Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 52: Expected string literal but 'null' literal was found. Use 'coerceInputValues = true' in 'Json {}` builder to coerce nulls to default values.

按照提示,需要在Json中添加coerceInputValues = true来解决这个问题,加上coerceInputValues之后如果属性有默认值,将会启用默认值

val data = Json{coerceInputValues = true}.decodeFromString<Project>("""
        {"name":"kotlinx.serialization","language":null}
    """)
    println(data)
   // 输出 Project(name=kotlinx.serialization, language=Kotlin)

嵌套引用

对于嵌套引用其他对象,要求引用的对象类也必须使用@Serializable注解

@Serializable
class Project(val name: String, val owner: User)

@Serializable
class User(val name: String)

fun main() {
    val owner = User("kotlin")
    val data = Project("kotlinx.serialization", owner)
    println(Json.encodeToString(data))
}

泛型类

不论是序列化或者反序列化,对于泛型类中实际引用的对象类要求也必须使用@Serializable注解,否则会报错

@Serializable
class Box<T>(val contents: T)
@Serializable
class Data(
    val a: Box<Int>,
    val b: Box<Project>
)
@Serializable
class Project(val name: String, val language:String)

fun main() {
    val data = Data(Box(42), Box(Project("kotlinx.serialization", "Kotlin")))
    println(Json.encodeToString(data))
}

属性别名

默认情况下原JSON串中的字段名字应该和类中的属性名字保持一致,但是有时候后端返回的JSON字段名字并不规范,为了提高可读性,可以使用@SerialName注解为属性起一个别名,这个别名将作为一个有实际作用的名字参与到序列化和反序列化过程中

//序列化别名
@Serializable
class Project(val name: String, @SerialName("lang") val language: String)

fun main() {
    val data = Project("kotlinx.serialization", "Kotlin")
    println(Json.encodeToString(data))
    //输出 {"name":"kotlinx.serialization","lang":"Kotlin"}
}
//反序列化别名
@Serializable
data class Project(val name: String, @SerialName("lang"), val language: String = "Kotlin")

fun main() {
    val data = Json.decodeFromString<Project>("""
        {"name":"kotlinx.serialization","language":null}
    """)
    println(data)
}

结语

kotlinx-serialization是jetbins官方出的序列化库,可以有效帮我们解决使用GSON等其他解析库带来的一些列与kotlin不兼容的问题,这一小节介绍了serialization的序列化和反序列化的基本操作,下一节会介绍serialization支持的数据类型。