作者:Viata
鏈接:https://juejin.cn/post/6913512989916135432
在相當長的一段時間里,kotlin一直都沒有自己專屬的序列化/反序列化庫,于是只能拿Java的庫來將就一下,最常用的大概就是Gson了,但是這樣一來Kt的很多強大特性就用不了,比如引數默認值,屬性委托等,就這樣被迫退化為Javaer了(沒錯,在下正是kotlin吹,Java叛徒), 雖然社區也維護了支持Kt特性的第三方序列化庫,比如moshi,but并不好用,Gson用習慣了就喜歡這種簡潔直白的女孩子(bushi),想了解Moshi的自己去查吧,個人認為官方庫出來后Moshi離完蛋不遠了,
Gson 在開始介紹今天的主角之前,先來回顧一下Gson在kt中的用法,與Java沒啥區別:
//使用資料類的原因是物件可以直接列印出來
data class Student(val name: String, val score: Int = 80)
fun main(){
val gosn = Gson()
val Icarus = Student("Icarus", 99)
println(gson.toJson(Icarus))//{"name":"Icarus","score":99}
val Icarus2 = gson.fromJson("""{"name":"Icarus","score":99}""", Student::class.java)
println(Icarus2)//Student(name=Icarus, score=99)
println(Icarus == Icarus2)//true
//下面是引數有預設值的情況
val SoharaMitsuki = Student("SoharaMitsuki")
println(gson.toJson(SoharaMitsuki ))//{"name":"SoharaMitsuki","score":80}
val SoharaMitsuki2 = gson.fromJson("""{"name":"SoharaMitsuki"}""", Student::class.java)
println(SoharaMitsuki2)//{"name":"SoharaMitsuki2","score":0}
}
注意到,我們定義的score屬性有默認值,我就直接說結論了,使用默認值生成的物件序列化成Json字串一切正常,但是Gson使用未給出屬性值的Json字串反序列化為Student物件,score屬性值是0,不會用到默認值的,因為Gson會先去類定義里面找對應的建構式,就是引數串列不帶這個屬性的建構式,沒找到就會用到Java黑魔法Unsafe類,直接創建物件, data class Student WithInits(val name: String, val score: Int){
val firstName by lazy {
name.split(" ")[0]
}
/**
*用by lazy跟隨的屬性沒有幕后欄位,初始化時不會再記憶體中給它開一個存盤值的空間
*初次使用該屬性時才會lazy后面的代碼,把參考指向代碼回傳的那塊記憶體
*專業名稱是延遲初始化
*/
val lastName by lazy {
name.split(" ")[1]
}
}
正是因為by lazy跟隨的屬性可以在運行時算出來,所以序列化的時候他們會被忽略從而減小Json長度,因為延遲初始化屬性在物件生成的時候只是一個空參考,Gson從json字串取回的物件相應屬性也是null,Gson把KClass當作JavaClass對待,延遲執行的代碼資訊也丟了, 如果你一定要既用Gson又要延遲初始化, 百度搜索“@Poko“了解詳情,
主角是kotlinx.serialization 首先配置Gradle:
//build.gradle
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.4.20'
id 'org.jetbrains.kotlin.plugin.serialization' version '1.4.20'
}
repositories {
// Artifacts are also available on Maven Central
jcenter()
}
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"
}
對于JSON,我們使用Json.encodeToString擴展功能對資料進行編碼,它將可序列化的物件作為其引數在后臺進行序列化,并將其編碼為JSON字串, 讓我們從描述專案的類開始,并嘗試獲取其JSON表示形式,
@Serializable
data class Project(val name: String, val language: String)
fun main() {
val data = Project("kotlinx.serialization", "Kotlin")
println(Json.encodeToString(data))
//列印{"name":"kotlinx.serialization","language":"Kotlin"}
val data = Json.decodeFromString<Project>("""
{"name":"kotlinx.serialization","language":"Kotlin"}
""")
println(data)//Project(name=kotlinx.serialization, language=Kotlin)
}
再次提醒,并不是只有資料類才能序列化,只是為了反序列化時能把類直接列印出來, 敲黑板,@Serializable,可以加引數指定我們自定義的序列化器,無引數時使用系統給的Serializer, 幕后欄位序列化 僅對有后備欄位的類的屬性進行序列化,因此具有getter / setter但卻沒有幕后欄位的屬性不會被序列化,被委托的屬性也不會被序列化,
@Serializable
class Project(
// name is a property with backing field -- serialized
var name: String
) {
var stars: Int = 0 // property with a backing field -- serializedval
path: String // getter only, no backing field -- not serialized
get() = "kotlin/$name"
var id by ::name // delegated property -- not serialized
}
fun main() {
val data = Project("kotlinx.serialization").apply { stars = 9000 }
println(Json.encodeToString(data))
//{"name":"kotlinx.serialization","stars":9000}
}
如果我們想定義Project類,使其采用路徑字串,然后將其解構為相應的屬性,則我們可能很想撰寫類似以下代碼的內容:
@Serializable
class Project(path: String) {
val owner: String = path.substringBefore('/')
val name: String = path.substringAfter('/')
}
此類無法編譯,因為@Serializable注解要求該類的主建構式的所有引數均為屬性,一個簡單的解決方法是使用類的屬性定義一個私有的主建構式,然后將所需的建構式轉換為輔助建構式,
@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")))
//{"owner":"kotlin","name":"kotlinx.serialization"}
}
path不具有幕后欄位,不會被序列化, 資料驗證 另一種情況是你可能想引入不帶屬性的主建構式引數,在將其值存盤到屬性之前對其進行驗證,為了使其可序列化,應該在主建構式中將其替換為屬性,然后將驗證移至init {...}塊中:
@Serializable
class Project(val name: String) {
init {
require(name.isNotEmpty()) { "name cannot be empty" }
}
}
fun main() {
val data = Json.decodeFromString<Project>("""
{"name":""}
""")//Exception in thread "main" java.lang.IllegalArgumentException: name cannot be empty
println(data)
}
默認值
默認值屬性反序列化時會被自動填充,序列化時不會被寫入json,目的還是節省空間和帶寬,在大多數實際場景中,此類配置可以減少視徑訓亂,并節省要序列化的資料量,
0@Serializable
data class Project(val name: String, val language: String = "Kotlin")
fun main() {
val data = Json.decodeFromString<Project>("""
{"name":"kotlinx.serialization"}
""")
println(data)//Project(name=kotlinx.serialization, language=Kotlin)
val data1 = Project("kotlinx.serialization")
println(Json.encodeToString(data1))//{"name":"kotlinx.serialization"}
}
另一種類似情況是可空屬性默認值為null
@Serializable
class Project(val name: String, val renamedTo: String? = null)
fun main() {
val data = Project("kotlinx.serialization")
println(Json.encodeToString(data))//{"name":"kotlinx.serialization"}
}
當輸入中存在可選屬性時,該屬性的相應初始化器不會呼叫,此功能是為提高性能而設計的,因此請注意不要依賴初始化程式中的副作用,
fun computeLanguage(): String {
println("Computing")
return "Kotlin"
}
@Serializable
data class Project(val name: String, val language: String = computeLanguage())
fun main() {
val data = Json.decodeFromString<Project>("""
{"name":"kotlinx.serialization","language":"Kotlin"}
""")
println(data)//Project(name=kotlinx.serialization, language=Kotlin)
}
由于在輸入中指定了language屬性,因此在輸出中看不到“計算”字串,
@Required修飾的屬性其值必須顯式指明,
@Serializable
data class Project(val name: String, @Required val language: String = "Kotlin")
fun main() {
val data = Json.decodeFromString<Project>("""
{"name":"kotlinx.serialization"}
""")
println(data)//Exception in thread "main" kotlinx.serialization.MissingFieldException: Field 'language' is required, but it was missing
}
@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"}
""")/**
*Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException:
*Unexpected JSON token at offset 60: Encountered an unknown key 'language'.
*Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys.
*/
println(data)
}
Kt的序列化框架是嚴格支持Kt的型別系統的,所以下面的代碼有例外:
@Serializable
data class Project(val name: String, val language: String = "Kotlin")
fun main() {
val data = Json.decodeFromString<Project>("""
{"name":"kotlinx.serialization","language":null}
""")//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.
println(data)
}
套娃序列化
可序列化的類可以在其可序列化屬性中參考其他類,被參考的類也必須標記為@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))//{"name":"kotlinx.serialization","owner":{"name":"kotlin"}}
}
不壓縮重復參考
@Serializable
class Project(val name: String, val owner: User, val maintainer: User)
@Serializable
class User(val name: String)
fun main() {
val owner = User("kotlin")
val data = Project("kotlinx.serialization", owner, owner)
println(Json.encodeToString(data))
//{"name":"kotlinx.serialization","owner":{"name":"kotlin"},"maintainer":{"name":"kotlin"}}
}
Kotlin序列化設計用于純資料的編碼和解碼,它不支持使用重復的物件參考重建任意物件圖,嘗試序列化兩次參考同一物件的實體,就寫入字串兩次,所以不要出現實體的環狀參考,那就爆堆疊了,
泛型類
Kotlin中的泛型類提供型別多型行為,由Kotlin序列化在編譯時強制執行,例如,考慮一個泛型可序列化類Box , 我們在JSON中獲得的實際型別取決于為Box指定的實際編譯時型別引數,
@Serializable
class Box<T>(val contents: T)
@Serializable
class Data(
val a: Box<Int>,
val b: Box<Project>
)
fun main() {
val data = Data(Box(42), Box(Project("kotlinx.serialization", "Kotlin")))
println(Json.encodeToString(data))
//{"a":{"contents":42},"b":{"contents":{"name":"kotlinx.serialization","language":"Kotlin"}}}
}
鍵別名
默認情況下,在編碼表示形式中使用的屬性名稱(在我們的示例中為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"}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/380196.html
標籤:其他
上一篇:藍牙通信的簡要設計與開發
