簡體   English   中英

Kotlin How to map a Class Type to field Type inside model class using Kotlin Generics?

[英]Kotlin How to map a Class Type to field Type inside model class using Kotlin Generics?

美好的一天 Kotlin 開發人員。 I'm building an android app written in Kotlin which calls soap web services api using Retrofit & OkHttp. 我正在使用這個庫TikXml將 kotlin model 類解析為 Xml ,反之亦然。 如果需要,您可以查看文檔,這可能有助於理解我的問題。

以下是我正在調用的 soap api 的請求正文的 Xml 結構的兩個示例:

/tasksbyelementsquery

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:glob="http://sap.com/xi/SAPGlobal20/Global">
   <soap:Header/>
   <soap:Body>
      <!--The Wrapper Class for Data to be passed in the body -->
      <glob:SiteLogisticsTaskByElementsQuery_sync>
        {data for tasksbyelementsquery}
      </glob:SiteLogisticsTaskByElementsQuery_sync>
   </soap:Body>
</soap:Envelope>

/tasksbycomponentsquery

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:glob="http://sap.com/xi/SAPGlobal20/Global">
   <soap:Header/>
   <soap:Body>
      <!--The Wrapper Class for Data to be passed in the body -->
      <glob:SiteLogisticsTaskByComponentsQuery_sync>
        {data for tasksbycomponenetsquery}
      </glob:SiteLogisticsTaskByComponentsQuery_sync>
   </soap:Body>
</soap:Envelope>

我還有 10 個其他 api,它們具有類似的 Xml 結構。 As you can notice that the xml envelope, header & body share the same Xml structure across all apis, as good practices for code reusability reusability I would like to create a Generic Wrapper Class that generates the envelope, header & body tags for all apis, and只有數據結構發生了變化。

因此,使用 Tikxml 解析庫,我生成了以下通用 class:

GeneralRequestEnvelope.kt

import com.tickaroo.tikxml.annotation.Xml
import com.tickaroo.tikxml.annotation.Element

@Xml(
    name = "soap:Envelope",
    writeNamespaces = [
        "soap=http://schemas.xmlsoap.org/soap/envelope/",
        "glob=http://sap.com/xi/SAPGlobal20/Global"
    ]
)
class GeneralRequestEnvelope <Type> {
    @Element(name = "soap:Header")
    var header: String? = null

    @Element(name = "soap:Body")
    var body: Type? = null /**Must ALWAYS be a class but can be any XML defined class**/
}

然后,當我從我的活動中調用我的 api 時,我為應該附加並作為請求正文發送的正文指定 Class 類型:

//Inside MainActivity

suspend fun callTestSoapApi(
    body: TasksByElementsQuery,
    onDone: (response: GeneralResponseEnvelope) -> Unit
){
    /** Here I'll Define which class type will be used as Body in the General Request*/
    val request = GeneralRequestEnvelope<TasksByElementsQuery>()
    request.body = body
    Timber.e("request after appending body: ${request.body}")
    response(onDone){
        soapService.callSampleSoapApiAsync(request)
    }
}

當我構建項目時,kapt 向我拋出了這個構建錯誤。

~Project_Location\app\build\tmp\kapt3\stubs\developmentDebug\com\qwerty\soapapitest\codebase\models\envelopes\GeneralRequestEnvelope.java:13: error: The type of field 'body' in class com.qwerty.soapapitest. codebase.models.envelopes.GeneralRequestEnvelope 不是 class 也不是接口。 只有類或接口可以使用@Element 注解進行注解。 如果您嘗試注釋原語而不是 @PropertyElement 私有類型主體;

kapt 基本上期望 GeneralRequestEnvelope class 中的變量主體使用 class 類型

@Element(name = "soap:Body")
var body: Type? = null

那么我如何將這種通用Type map 轉換為一些通用的 class 之類的...... Type::class.java類的? 我不想為 12 個不同的 api 為相同的信封結構創建 12 個不同的類。 使用 Kotlin Generics 實現我的用例的任何幫助將不勝感激。

您在濫用 Retrofit。 不幸的是,由於 Java 及其孩子 Kotlin 最大和最荒謬的缺點,您將面臨所謂的 Java 類型擦除的影響。 在運行時,您的類型(泛型)無法解析。 Kotlin 有一個解決方法(效率不高....通過內聯函數的reified參數,但在最復雜的情況下並沒有太大幫助)。 我的建議是將 XML 解析器與 Retrofit 分開,當您從/向服務器接收/發送對象時,添加一個字段來指定 class 的名稱... that Retrofit will return the XML serialization of your object with its name, then through Reflection instantiate the object (better use libraries like GSon...or similar). You'll have to pass to the library the CLASS type of your object that you can determine from the name of the object received/sent and the library will instantiate the object for you (it is exaclty what Retrofit and its deserializer/serializer do) . 我添加了一個小代碼片段讓您了解如何(它基於 GSon 和 Retrofit... 用 Kotlin 編寫):

  val netMethod = api.getDataFromSAerver(sessionId, request)//This is the Retrofit method 
            val response = netMethod.execute()
            if (response.isSuccessful) {
                try {
                    val result = response.body()?.string()

                    if (!result.isNullOrEmpty()) {
                        val array = JSONObject(result).getJSONArray("objects")
                        val objects = mutableListOf<Any>()
                        for (i in 0 until array.length()) {
                            val objJson = array.get(i)
                            val s = objJson.toString()
                            val obj = jsonParser.fromJson(s, resultType.java)//This is the type used for deserialization of your object (as per GSon)
                            objects.add(obj)
                        }
                       
                        return Result(objects)
                    } else {
                        return Result(listOf<Unit>(), "no HTTP response", -1)
                    }

                } catch (e: Throwable) {

....            } else {

....
            }

截斷的代碼基於 JSon 響應(我強烈推薦 XML 用於網絡流量......它最適合 Restul 服務/微服務)。

暫無
暫無

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

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