Kotlin Gson RuntimeTypeAdapterFactory
Kotlin gson convert multiplier type RuntimeTypeAdapterFactory
gson
gson 是一個 android 開發者很常使用的 Json 轉換套件, 是 google 開發的
在一般公司來說 Server Api Call return 的內容裡面,總是會出現類似下列這樣的 JSON 內容格式
{
  "data": [
    {
      "type": "title",
      "name": "..."
    },
    {
      "type": "first",
      "data": [
        {
          "title": "",
          "description": ""
        }
      ]
    },
    {
      "type": "second",
      "data": [
        {
          "image_url": "https://xxx.xxx.xx/xx.jpg",
          "url": "https://xx.xxx.xx/xx/xx"
        }
      ]
    }
  ]
}
Array 裡面的的每一筆並不是一樣的格式
看到開始想的是,應該用什麼樣的 Model 去把些內容接下來,想不出來,就去跟後端說,這樣的格式不合理
後端把 前端 iOS 都找來問,前端 跟 iOS 都說他們可以接
那只好再想想辦法…
RuntimeTypeAdapterFactory 就是你需要的, 你可以在 gson 的 repo 裡面看到他
可是你若是在 Gradle 裡面,只有
  implementation 'com.google.code.gson:gson:2.8.5'
是無法使用到的
他是被放在
  implementation 'org.danilopianini:gson-extras:0.2.1'
使用的方式基本上如下 (以上方的 JSON 為解析對象)
建立相對應的 Model, 記得要繼承同一個,這裡用的是 BaseTestModel
  open class BaseTestModel(val type: String)
  data class FirstModel(val title: String, val description: String)
  data class SecondModel(val image_url: String, val url: String)
  class TestTitleModel(type: String, name: String) : BaseTestModel(type)
  class TestFirstModel(type: String, data: List<FirstModel>) : BaseTestModel(type)
  class TestSecondModel(type: String, data: List<SecondModel>) : BaseTestModel(type)
建立一個 convertFactory
  private object TestResultFactory {
    private val baseAdapterFactory = RuntimeTypeAdapterFactory.of(BaseTestModel::class.java)
        .registerSubtype(TestTitleModel::class.java, "title")
        .registerSubtype(TestFirstModel::class.java, "first")
        .registerSubtype(TestSecondModel::class.java, "second")
    private val gson = GsonBuilder().registerTypeAdapterFactory(baseAdapterFactory).create()
    fun create(jsonElement: JsonElement?): BaseTestModel? {
        return gson.fromJson<BaseTestModel?>(jsonElement, BaseTestModel::class.java)
    }
  }
建立 SubClass of JsonDeserializer
  class TestDataDeserializer : JsonDeserializer<BaseTestModel> {
      override fun deserialize(
          json: JsonElement?,
          typeOfT: Type?,
          context: JsonDeserializationContext?
      ): BaseTestModel? {
          return TestResultFactory.create(json)
      }
  }
放在 gson Build 裡面去註冊
  GsonBuilder()
    .registerTypeAdapter(BaseTestModel::class.java, TestDataDeserializer())
    .create()
搞定, 可以順利把整個都正確的接下來了
當然取用的時候,記得要轉型,才可以取得每一個型別不同的內容
看來一切如此美好,自然就上線了
某天,後端給的新的一個 type “main”, app 顯示一片空白
千追萬追才發現,RuntimeTypeAdapterFactory 處理到沒有註冊的 type 就會 throw error
要怎樣避免加了一個新的 type 就讓整個解析失敗呢
那就不要傳入沒有註冊的 type 去解析
簡單說就是對 TestResultFactory 動一點手腳
  private object TestResultFactory {
    private val supportTypes: List<String> = listOf("title", "first", "second")
    private val baseAdapterFactory = RuntimeTypeAdapterFactory.of(BaseTestModel::class.java)
        .registerSubtype(TestTitleModel::class.java, "title")
        .registerSubtype(TestFirstModel::class.java, "first")
        .registerSubtype(TestSecondModel::class.java, "second")
    private val gson = GsonBuilder().registerTypeAdapterFactory(baseAdapterFactory).create()
    fun create(jsonElement: JsonElement?): BaseTestModel? {
        // 修改在這裡,判斷如果是 support 的 type 才去解析,否則直接 return Base Type
        return jsonElement?.asJsonObject?.deepCopy()?.remove("type")?.asString?.takeIf {
            it.isNotEmpty() && supportTypes.contains(it)
        }?.let {
            gson.fromJson<BaseTestModel?>(jsonElement, BaseTestModel::class.java)
        } ?: BaseTestModel("")
    }
  }
記得要解析完,要排除基本的 type,因為那是你沒有 Support 的 Type
打完收工