主頁 > 移動端開發 > 千呼萬喚始出來,Kotlin官方序列化庫終相見(一)

千呼萬喚始出來,Kotlin官方序列化庫終相見(一)

2021-12-14 08:01:00 移動端開發

作者: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

標籤:其他

上一篇:藍牙通信的簡要設計與開發

下一篇:12月上岸,Android研發 4 面,成功 “跳”進阿里,獲60w Offer

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more