2017-06-22 4 views
1

Derzeit verwende ich retrofit2, um Ruhe apis aufrufen und Antwort erhalten. Da der Antworttext mehrere Typen haben kann, habe ich den folgenden Code geschrieben.Retrofit 2 Response Body zu benutzerdefinierten Klasse

//Interface 
@FormUrlEncoded 
@POST("payments/events/{id}") 
fun postPayment(@Path("id") id: String): Call<Any> 

//Api Manager 
fun postPayment(id: String): Observable<Any> { 
    return Observable.create { 
     subscriber -> 
     val callResponse = api.postPayment(id) 
     val response = callResponse.execute() 

     if (response.isSuccessful) { 
      if (response.body() is MyClass1) { 
       // never success... 
      } else if (response.body() is MyClass2) { 
       // never success... 
      } 
      subscriber.onNext(response.body()) 
      subscriber.onCompleted() 
     } else { 
      subscriber.onError(Throwable(response.message())) 
     } 
    } 
} 

So bin ich nicht in der Lage response.body() zu MyClass1 oder MyClass2 zu werfen. response.body() as MyClass1 tritt auch Fehler auf.

MyClass1 und MyClass2 sind normale Vorlagenklassen.

Gibt es eine intelligente Möglichkeit, Antwortkörper auf meine benutzerdefinierten Klassen zu übertragen?

Kleines Update für MyClass2

class MyClass2(val token: String, val url: String, val quantity: Int) 
+0

Warum verwenden Sie kein benutzerdefiniertes Objekt für den Empfang Ihrer Antwort? etwas wie 'Call ' – Marce

+0

Wie ich beschrieben, in meinem Fall kann der Antworttyp mehrere Typen sein - MyClass1 oder MyClass2. – Igor

+0

Gibt es ('MyClass1' und' MyClass2') zu viele Unterschiede? – Marce

Antwort

1

Zunächst einmal danke @ Bryan für die Antwort. Deine Antwort war perfekt, aber schließlich habe ich etwas kniffliges getan.

... 
if (response.isSuccessful) { 
    val jsonObject = JSONObject(response.body() as Map<*, *>) 
    val jsonString = jsonObject.toString() 
    if (jsonObject.has("id")) { 
     val myclass1Object = Gson().fromJson(jsonString, MyClass1::class.java) 
     ... 
    } else { 
     val myclass2Object = Gson().fromJson(jsonString, MyClass2::class.java) 
     ... 
    } 
} 
... 
1

Wie bereits erwähnt von @ Miha_x64, Retrofit nicht weiß, über Ihre Klassen (MyClass1 und MyClass2), weil Ihr Call verwendet die Any Typ. Retrofit erstellt daher keine Instanz von MyClass1 oder MyClass2, sondern erstellt lediglich eine Instanz der Klasse Any.

Die einfachste Lösung wäre, nur die beiden Klassen kombinieren:

data class MyClass(
    val id: String?, 
    val data: String?, 
    val token: String?, 
    val url: String?, 
    val quantity: Int 
) 

Dann können Sie den Antworttyp in Ihrer Schnittstelle angeben:

@FormUrlEncoded 
@POST("payments/events/{id}") 
fun postPayment(@Path("id") id: String): Call<MyClass> 

Im Fall Ihre Antwort nicht hat id oder data Element werden sie nur null sein. Dann können Sie überprüfen, welche Art von Antwort empfangen wurde einfach durch Überprüfung, welche Werte null:

if (response.body().id != null) { 
    // Handle type 1 response... 
} else if (response.body().token != null) { 
    // Handle type 2 response... 
} 

Eine etwas komplexere Lösung wäre, einen Wrapper für Ihre zwei Klassen zu schreiben, und eine Art Adapter zum Auffüllen die Verpackung. Dies würde die NULL-Zulässigkeit jedes der Felder vermeiden und Ihre Datenstruktur getrennt halten.

Dies ist die basierend auf ConverterFactory Sie verwenden, aber wenn zum Beispiel unterscheiden würden Sie Gson verwenden, würde es in etwa so aussehen:

data class ApiResponse(
    val val1: MyClass1? = null, 
    val val2: MyClass2? = null 
) 

class ApiResponseAdapter : TypeAdapter<ApiResponse> { 

    @Throws(IOException::class) 
    override fun write(out: JsonWriter, value: ApiResponse?) { 
     if (value != null) { 
      out.beginObject() 

      value.val1?.id? let { out.name("id").value(it) } 
      value.val1?.data? let { out.name("data").value(it) } 
      value.val2?.token? let { out.name("token").value(it) } 
      value.val2?.url? let { out.name("url").value(it) } 
      value.val2?.quantity? let { out.name("quantity").value(it) } 

      out.endObject() 
     } else { 
      out.nullValue() 
     } 
    } 

    @Throws(IOException::class) 
    override fun read(in: JsonReader): ApiResponse { 
     reader.beginObject() 

     var id: String? = null 
     var data: String? = null 
     var token: String? = null 
     var url: String? = null 
     var quantity: Int = 0 

     while(in.hasNext()) { 
      val name = in.nextName() 

      if (name.equals("id", true)) { 
       id = in.nextString() 
      } else if (name.equals("data", true)) { 
       data = in.nextString() 
      } else if (name.equals("token", true)) { 
       token = in.nextString() 
      } else if (name.equals("url", true)) { 
       url = in.nextString() 
      } else if (name.equals("quantity", true)) { 
       quantity = in.nextInt() 
      } 
     } 

     reader.endObject() 

     if (id != null && data != null) { 
      return ApiResponse(MyClass1(id, data), null) 
     } else if (token != null && url != null) { 
      return ApiResponse(null, MyClass2(token, url, quantity)) 
     } else { 
      return ApiResponse() 
     } 
    } 

} 

Dann können Sie diese Art Adapter an Ihren Gson Instanz hinzufügen :

val gson = GsonBuilder().registerTypeAdapter(ApiResponse::class.java, ApiResponseAdapter()).create() 

den Typ Call<Any> mit Call<ApiRepsone> ersetzen dann können und Sie dann überprüfen, welche Antwort von Prüfung empfangen wurde, der Wert null ist:

if (response.body().val1 != null) { 
    // Handle MyClass1 response... 
} else if (response.body().val2 != null) { 
    // Handle MyClass2 response... 
} 
Verwandte Themen