簡體   English   中英

為什么 Kotlin 數據類可以在 Gson 的不可為空字段中具有空值?

[英]Why Kotlin data classes can have nulls in non-nullable fields with Gson?

在 Kotlin 中,您可以創建一個data class

data class CountriesResponse(
    val count: Int,
    val countries: List<Country>,
    val error: String)

然后您可以使用它來解析 JSON,例如“{n: 10}”。 在這種情況下,您將有一個對象val countries: CountriesResponse ,從RetrofitFuelGson接收,其中包含以下值: count = 0, countries = null, error = null

Kotlin + Gson - How to get an emptyList when null for data class你可以看到另一個例子。

當您稍后嘗試使用countries ,您將在此處得到一個異常: val size = countries.countries.size : "kotlin.TypeCastException: null cannot be cast to non-null type kotlin.Int"。 如果您編寫代碼並使用? 訪問這些字段時,Android Studio 會突出顯示?. 並警告: Unnecessary safe call on a non-null receiver of type List<Country>

那么,我們應該使用? 在數據類中? 為什么應用程序可以在運行時將null設置為不可為 null 的變量?

發生這種情況是因為 Gson 使用不安全(如在java.misc.Unsafe )實例構造機制來創建類的實例,繞過它們的構造函數,然后直接設置它們的字段。

有關一些研究,請參閱此問答: Gson Deserialization with Kotlin, Initializer block not called

因此,Gson 忽略了構造邏輯和類狀態不變量,因此不建議將其用於可能受此影響的復雜類。 它也會忽略 setter 中的值檢查。

考慮 Kotlin-aware 序列化解決方案,例如 Jackson(在上面鏈接的問答中提到)或kotlinx.serialization

許多 Json 解析器(包括 Gson)的設計決定是讓它默默地吞下不存在的字段。 對象被設置為null ,原語被設置為0 ,完全掩蓋了 API 錯誤並導致更下游的神秘錯誤。

出於這個原因,我推薦兩階段解析:

package com.example.transport
//this class is passed to Gson (or any other parser)
data class CountriesResponseTransport(
   val count: Int?,
   val countries: List<CountryTransport>?,
   val error: String?){

   fun toDomain() = CountriesResponse(
           count ?: throw MandatoryIsNullException("count"),
           countries?.map{it.toDomain()} ?: throw MandatoryIsNullException("countries"),
           error ?: throw MandatoryIsNullException("error")
       )
}

package com.example.domain
//this one is actually used in the app
data class CountriesResponse(
   val count: Int,
   val countries: Collection<Country>,
   val error: String)

是的,它的工作量是原來的兩倍 - 但它會立即查明 API 錯誤,並在您無法修復這些錯誤時為您提供處理這些錯誤的地方,例如:

   fun toDomain() = CountriesResponse(
           count ?: countries?.count ?: -1, //just to brag we can default to non-zero
           countries?.map{it.toDomain()} ?: ArrayList()
           error ?: MyApplication.INSTANCE.getDeafultErrorMessage()
       )

是的,您可以使用具有更多選項的更好的解析器 - 但您不應該這樣做。 您應該做的是將解析器抽象出來,以便您可以使用任何解析器。 因為無論您今天找到多么先進和可配置的解析器,最終您都會需要它不支持的功能。 這就是我將 Gson 視為最小公分母的原因。

有一篇文章解釋了在存儲庫模式的更大上下文中使用(和擴展)的這個概念。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM