How to deserialize JSON response in GSON for inconsistent API response? - android

I have looked into this issue, and tried to implement what i could find over the SO, but no luck. So is my problem:
Sometimes, in my JSON response, I get the data field as a JSON object, and other times the data field as JSON array. Technically this is a bad API design, however changing the API at this point is not feasible.
Two types of JSON responses by API
{
data: {
.
.
.
}
}
{
data : [
.
.
.
]
}
I have tried to implement the DataDeserializer, however the GSON still identifies the JSON response as JSON object and not able to use the DataDeserializer:
Class DataDeserializer
class DataDeserializer : JsonDeserializer<List<Data<Any>>> {
override fun deserialize(
json: JsonElement,
typeOfT: Type?,
context: JsonDeserializationContext
): List<Data<Any>> {
val dataList = ArrayList<Data<Any>>()
when {
json.isJsonObject -> {
val data = context.deserialize<Data<Any>>(json.asJsonObject, Data::class.java)
dataList.add(data)
}
json.isJsonArray -> {
for (jsonObject in json.asJsonArray)
dataList.add(context.deserialize(jsonObject, Data::class.java))
}
else -> throw RuntimeException("Unexpected JSON Type: ${json.javaClass}")
}
return dataList
}
}
Model Classes
open class Json<T> {
lateinit var data: List<Data<T>>
fun getFirstChild() = data.first()
}
data class Data<T>(
private val id: String = "",
private val type: String = "",
val attributes: T
)
Registering DataDeserializer with GSONConverterFactory
val gson = GsonBuilder().registerTypeAdapter(Data::class.java, DataDeserializer()).create()

Related

How to make a generic serialized name in retrofit?

Most of my API calls return something like this:
{
"message": str,
"status": str,
"different_name_depending_on_endpoint":{
// bunch of stuff
},
}
How can I make the name that goes in the "different_name_depending_on_endpoint" key generic? Like so :
data class ReturnAPI<T>(
#SerializedName("message")
val message: String,
#SerializedName("status")
val status: String,
#SerializedName("how do I make this generic")
val data: T
)
Otherwise for every endpoint I have to create a separate data class for the api return, and a separate data class for the data field, which is impractical. There must be a way to make it generic, right? Or to tell Retrofit to try to fill the data field with whatever else is in the JSON file that isn't message or status.
Or to tell Retrofit to
try to fill the data field with whatever else is in the JSON file that
isn't message or status
Yes, you can write a custom deserializer for ReturnAPI type:
#JsonAdapter(ReturnApiTypeAdapter::class)
data class ReturnAPI<T>(
val message: String,
val status: String,
val data: T
)
class ReturnApiTypeAdapter : JsonDeserializer<ReturnAPI<*>> {
#Throws(JsonParseException::class)
override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): ReturnAPI<*>? {
if (json.isJsonNull) {
return null
}
val jsonObject = json.asJsonObject
// get actual type of Data
val typeOfData = (typeOfT as ParameterizedType)
.actualTypeArguments[0]
var message: String? = null
var status: String? = null
var data: Any? = null
for (key in jsonObject.keySet()) {
when (key) {
"message" -> message = jsonObject.getAsJsonPrimitive(key).asString
"status" -> status = jsonObject.getAsJsonPrimitive(key).asString
else -> data = context.deserialize(jsonObject.get(key), typeOfData)
}
}
// optional checks
check(message != null && status != null) {
"Failed parsing"
}
return ReturnAPI(message, status, data)
}
}

Formatting the Nested JSON response from retrofit API in MVVM Architecture - Kotlin

I m new to kotlin and MVVM, I have been working around this issue for a week now, couldn't get any idea even after searching for some code on the internet.
I'm trying to edit or modify the retrofit response (to observe a specific type; say "sf") according to my need and neglecting other data which is not needed. I'm using mutable livedata to fetch and update the JSON data from the retrofit response to the recylerview.
Here is the link for the JSON data: http://www.nactem.ac.uk/software/acromine/dictionary.py?sf=HMM
3 Data classes based on JSON response:
data class sf(
#SerializedName("sf")
#Expose
val sf : String? = null,
#SerializedName("lfs")
#Expose
val lfs : List<lfs>? = null,
)
data class lfs(
#SerializedName("lf")
#Expose
var lf : String? = null,
#SerializedName("freq")
#Expose
var freq : Int? = null,
#SerializedName("since")
#Expose
var since : Int? = null,
#SerializedName("vars")
#Expose
var vars : List<vars>? = null,
) : Serializable
class vars (
#SerializedName("lf")
#Expose
var lf : String? = null,
#SerializedName("freq")
#Expose
var freq : Int? = null,
#SerializedName("since")
#Expose
var since : Int?
): Serializable
Code in Activity:
listUsers = mutableListOf()
adapter = WordAdapters(this, listUsers )
recyclerView.adapter = adapter
wordViewModel = ViewModelProviders.of(this,
WordViewModelFactory(this)).get(WordsViewModel::class.java)
wordViewModel!!.getData().observe(this, { t: ArrayList<sf>? ->
listUsers.clear()
t?.let { listUsers.addAll(it)
}
adapter.notifyDataSetChanged()
})
ViewModel:
class WordsViewModel ( context: Context) : ViewModel() {
private var listData = MutableLiveData<ArrayList<sf>>()
init {
val wordRepository: WordsRepository by lazy {
WordsRepository
}
//if (context.isInternetAvailable()) {
listData = wordRepository.getMutableLiveData(context)
// }
}
fun getData(): MutableLiveData<ArrayList<sf>> {
return listData
} }
Repository:
object WordsRepository {
var call: Call<MutableList<sf>>? = null
fun getMutableLiveData(context: Context) : MutableLiveData<ArrayList<sf>> {
val mutableLiveData = MutableLiveData<ArrayList<sf>>()
//context.showProgressBar()
call = NetworkApiClient.apiService.getWordsMatching("HMM")
call!!.enqueue(object : Callback<MutableList<sf>> {
override fun onFailure(call: Call<MutableList<sf>>, t: Throwable) {
//hideProgressBar()
Log.e("error", t.localizedMessage.toString())
}
override fun onResponse(call: Call<MutableList<sf>>, response:
Response<MutableList<sf>>)
{
//hideProgressBar()
if (!response.isSuccessful){
Log.e("Code " , response.code().toString());
return
}
val raw: okhttp3.Response = response.raw()
val usersResponse : MutableList<sf>? = response.body()
/* if (usersResponse != null) {
for( movie in usersResponse[0].lfs!!){
Log.v("MainActivity", movie.vars.toString())
}
}*/
Log.e("Output : ", usersResponse.toString())
usersResponse?.let { mutableLiveData.value = it as ArrayList<sf> }
}
})
return mutableLiveData
}
}
this is the base structure of JSON: here "sf" is a string, lfs is the array, according to this JSON response link provided I get 8 lfs arrays, but currently after parsing the recyclecount is 1 which is the same in the adapter itemcount method, so I get one row displayed in recylerview and rest are ignored.
JSON response:
[
{
"sf":"HMM",
"lfs":[
{
"lf":"heavy meromyosin",
"freq":267,
"since":1971,
"vars":[
{
"lf":"heavy meromyosin",
"freq":244,
"since":1971
},
{
"lf":"Heavy meromyosin",
"freq":12,
"since":1975
},
{
"lf":"H-meromyosin",
"freq":5,
"since":1975
},
{
"lf":"heavy-meromyosin",
"freq":4,
"since":1977
},
{
"lf":"heavy meromyosin",
"freq":1,
"since":1976
},
{
"lf":"H-Meromyosin",
"freq":1,
"since":1976
}
]
},
I want to ignore "sf" string after response and parse the ArrayList which is present under the "sf" which is "lfs", so based on "lfs" I need to display the data.
Mutable live data is not accepting any other type other than sf, since I placed the observer on it.
On the json you posted, there is only one parent item ( one sf ), but you are actually trying to pass the 8 lfs children. You have to perform such transformation somewhere, it could be on the network call directly, like this:
usersResponse?.let { mutableLiveData.value = it[0].lfs as ArrayList }
Take into account two things:
It could be better to check if "it" is not empty before going for the first item.
This only works if you will always have only one item on the parent array ( this sounds strange since if this is the case then the service should be returning an object, not a list, as the root of the json. If you will receive more than one object you will have to map the response into a single list of lfs. Something like (pseudo code since I'm from my phone):
It.map( item -> item.lfs)

Consuming Polymorphic Jsons with Retrofit and Kotlin

My API sends me a polyphonic Json in with the variable addon_item can be either a String or an Array, I have spend days trying to make a CustomDezerializer for it without any success.
Here is the Json response:
({
"code": 1,
"msg": "OK",
"details": {
"merchant_id": "62",
"item_id": "1665",
"item_name": "Burrito",
"item_description": "Delicioso Burrito en base de tortilla de 30 cm",
"discount": "",
"photo": "http:\/\/www.asiderapido.cloud\/upload\/1568249379-KDKQ5789.jpg",
"item_cant": "-1",
"cooking_ref": false,
"cooking_ref_trans": "",
"addon_item": [{
"subcat_id": "144",
"subcat_name": "EXTRA",
"subcat_name_trans": "",
"multi_option": "multiple",
"multi_option_val": "",
"two_flavor_position": "",
"require_addons": "",
"sub_item": [{
"sub_item_id": "697",
"sub_item_name": "Queso cheddar",
"item_description": "Delicioso queso fundido",
"price": "36331.20",
"price_usd": null
}]
}]
}
})
Here is the Custom Dezerializer, which includes BodyConverter that removes two braces that encompassed the Json response:
'''
/**
* This class was created due to 2 issues with the current API responses:
* 1. The API JSON results where encapsulated by parenthesis
* 2. They had dynamic JSON variables, where the Details variable was coming as a String
* or as an Object depending on the error message (werer whe user and password wereh correct.
*
*/
class JsonConverter(private val gson: Gson) : Converter.Factory() {
override fun responseBodyConverter(
type: Type?, annotations: Array<Annotation>?,
retrofit: Retrofit?
): Converter<ResponseBody, *>? {
val adapter = gson.getAdapter(TypeToken.get(type!!))
return GsonResponseBodyConverter(gson, adapter)
}
override fun requestBodyConverter(
type: Type?,
parameterAnnotations: Array<Annotation>?,
methodAnnotations: Array<Annotation>?,
retrofit: Retrofit?
): Converter<*, RequestBody>? {
val adapter = gson.getAdapter(TypeToken.get(type!!))
return GsonRequestBodyConverter(gson, adapter)
}
internal inner class GsonRequestBodyConverter<T>(
private val gson: Gson,
private val adapter: TypeAdapter<T>
) : Converter<T, RequestBody> {
private val MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8")
private val UTF_8 = Charset.forName("UTF-8")
#Throws(IOException::class)
override fun convert(value: T): RequestBody {
val buffer = Buffer()
val writer = OutputStreamWriter(buffer.outputStream(), UTF_8)
val jsonWriter = gson.newJsonWriter(writer)
adapter.write(jsonWriter, value)
jsonWriter.close()
return RequestBody.create(MEDIA_TYPE, buffer.readByteString())
}
}
// Here we remove the parenthesis from the JSON response
internal inner class GsonResponseBodyConverter<T>(
gson: Gson,
private val adapter: TypeAdapter<T>
) : Converter<ResponseBody, T> {
#Throws(IOException::class)
override fun convert(value: ResponseBody): T? {
val dirty = value.string()
val clean = dirty.replace("(", "")
.replace(")", "")
try {
return adapter.fromJson(clean)
} finally {
value.close()
}
}
}
class DetalleDeProductoDeserializer : JsonDeserializer<DetallesDelItemWrapper2> {
override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): DetallesDelItemWrapper2 {
if ((json as JsonObject).get("addon_item") is JsonObject) {
return Gson().fromJson<DetallesDelItemWrapper2>(json, ListaDetalleAddonItem::class.java)
} else {
return Gson().fromJson<DetallesDelItemWrapper2>(json, DetallesDelItemWrapper2.CookingRefItemBoolean::class.java)
}
}
}
companion object {
private val LOG_TAG = JsonConverter::class.java!!.getSimpleName()
fun create(detalleDeProductoDeserializer: DetalleDeProductoDeserializer): JsonConverter {
Log.e("Perfill Adapter = ", "Test5 " + "JsonConverter" )
return create(Gson())
}
fun create(): JsonConverter {
return create(Gson())
}
private fun create(gson: Gson?): JsonConverter {
if (gson == null) throw NullPointerException("gson == null")
return JsonConverter(gson)
}
}
}
Here is the RetrofitClient.class:
class RetrofitClient private constructor(name: String) {
private var retrofit: Retrofit? = null
fun getApi(): Api {
return retrofit!!.create(Api::class.java)
}
init {
if (name == "detalleDelItem") run {
retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(JsonConverterJava.create(JsonConverterJava.DetallesDelItemDeserializer()))
// .addConverterFactory(GsonConverterFactory.create(percentDeserializer))
.client(unsafeOkHttpClient.build())
.build()
Log.e("RetrofitClient ", "Instace: " + "detalle " + name)
}
}
companion object {
//Remember this shit is https for the production server
private val BASE_URL = "http://www.asiderapido.cloud/mobileapp/api/"
private var mInstance: RetrofitClient? = null
#Synchronized
fun getInstance(name: String): RetrofitClient {
mInstance = RetrofitClient(name)
return mInstance!!
}
}
}
Finally my POJO:
open class DetallesDelItemWrapper2 {
#SerializedName("code")
val code: Int? = null
#Expose
#SerializedName("details")
var details: ItemDetails? = null
#SerializedName("msg")
val msg: String? = null
class ItemDetails {
#Expose
#SerializedName("addon_item")
val addonItem: Any? = null
#SerializedName("category_info")
val categoryInfo: CategoryInfo? = null
#SerializedName("cooking_ref")
val cookingRef: Any? = null
#SerializedName("cooking_ref_trans")
val cookingRefTrans: String? = null
}
class ListaDetalleAddonItem: DetallesDelItemWrapper2(){
#SerializedName("addon_item")
val detalleAddonItem: List<DetalleAddonItem>? = null
}
class StringDetalleAddonItem: DetallesDelItemWrapper2(){
#SerializedName("addon_item")
val detalleAddonItem: String? = null
}
I took a shot at this and came up with 2 possible ideas. I don't think they're the only way to achieve this, but I think I can share my thoughts.
First, I've reduced the problem to actually only parsing the items. So I've removed retrofit from the equation and use the following jsons:
val json = """{
"addon_item": [{
"subcat_id": "144",
"subcat_name": "EXTRA",
"subcat_name_trans": "",
"multi_option": "multiple",
"multi_option_val": "",
"two_flavor_position": "",
"require_addons": "",
"sub_item": [{
"sub_item_id": "697",
"sub_item_name": "Queso cheddar",
"item_description": "Delicioso queso fundido",
"price": "36331.20",
"price_usd": null
}]
}]
}
""".trimIndent()
(for when the addon_item is an array)
val jsonString = """{
"addon_item": "foo"
}
""".trimIndent()
(for when the addon_item is a string)
First approach
My first approach was to model addon_item as a generic JsonElement:
data class ItemDetails(
#Expose
#SerializedName("addon_item")
val addonItem: JsonElement? = null
)
(I'm using data classes because I find them more helpful, but you don't have too)
The idea here is to let gson deserialize it as a generic json element and you can then inspect it yourself. So if we add some convenience methods to the class:
data class ItemDetails(
#Expose
#SerializedName("addon_item")
val addonItem: JsonElement? = null
) {
fun isAddOnItemString() =
addonItem?.isJsonPrimitive == true && addonItem.asJsonPrimitive.isString
fun isAddOnItemArray() =
addonItem?.isJsonArray == true
fun addOnItemAsString() =
addonItem?.asString
fun addOnItemAsArray() =
addonItem?.asJsonArray
}
So as you can see, we check the addOnItem for what it contains and according to that, we can obtain its contents. Here's an example of how to use it:
fun main() {
val item = Gson().fromJson(jsonString, ItemDetails::class.java)
println(item.isAddOnItemArray())
println(item.isAddOnItemString())
println(item.addOnItemAsString())
}
I think the biggest advantage of this is that it's fairly simple and you don't require custom logic to deserialize. For me, the huge drawback is the type-safety loss.
You can get the add on as an array, but it will be an array of json elements that have to be "manually" deserialized. Hence, my 2nd approach tries to tackle this.
Second approach
The idea here is to use Kotlin's sealed classes and have 2 types of add ons:
sealed class AddOnItems {
data class StringAddOnItems(
val addOn: String
) : AddOnItems()
data class ArrayAddOnItems(
val addOns: List<SubCategory> = emptyList()
) : AddOnItems()
fun isArray() = this is ArrayAddOnItems
fun isString() = this is StringAddOnItems
}
The SubCategory class is just what was inside the list. Here's a simple version of it:
data class SubCategory(
#SerializedName("subcat_id")
val id: String
)
As you can see the AddOnItems is a sealed class that has the only 2 possible types for your use case.
Now we need a custom deserializer:
class AddOnItemsDeserializer : JsonDeserializer<AddOnItems> {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?) =
when {
json?.isJsonArray == true -> {
AddOnItems.ArrayAddOnItems(context!!.deserialize(
json.asJsonArray,
TypeToken.getParameterized(List::class.java, SubCategory::class.java).type))
}
json?.isJsonPrimitive == true && json.asJsonPrimitive.isString ->
AddOnItems.StringAddOnItems(json.asJsonPrimitive.asString)
else -> throw IllegalStateException("Cannot parse $json as addonItems")
}
}
In a nutshell, this checks if add on is an array and creates the respective class and the same for string.
Here's how you can use it:
fun main() {
val item = GsonBuilder()
.registerTypeAdapter(AddOnItems::class.java, AddOnItemsDeserializer())
.create()
.fromJson(jsonString, ItemDetails::class.java)
println(item.addOnItems.isString())
println(item.addOnItemsAsString().addOn)
val item = GsonBuilder()
.registerTypeAdapter(AddOnItems::class.java, AddOnItemsDeserializer())
.create()
.fromJson(json, ItemDetails::class.java)
println(item.addOnItems.isArray())
println(item.addOnItemsAsArray().addOns[0])
}
I think the biggest advantage here is that you get to keep the types. However, you still need to check what it is before calling addOnItemsAs*.
Hope this helps

Proper way for json parsing for mutable structure with gson

I'm getting response from server based on such structure:
{
"success":true,
"data":{"can be some kind of data, array or error message"}
}
What is the right way to map properly data attribute in such situations?
My attempts was to use Any type and cast after to specified type:
data class GeneralResponseModel(
val success: Boolean,
val data: Any
)
Provider
//
val response = gson.fromJson(it[0].toString(), GeneralResponseModel::class.java)
//
ViewModel
////////
if (res.success) {
isLoading.postValue(false)
///////
} else {
val result = res.data as ResponseError
errorMessage.postValue(ErrorWrapper(ErrorType.REQUEST_ERROR,result.detail,result.title))
isLoading.postValue(false)
}
///////////
And I got
io.reactivex.exceptions.OnErrorNotImplementedException:
com.google.gson.internal.LinkedTreeMap cannot be cast to
com.myapp.model.response.ResponseError
Another attempt was in using empty interface which was implemented by all possible response types. In this situation I got
java.lang.RuntimeException: Unable to invoke no-args constructor for
interface com.myapp.model.response.Response.
Registering an InstanceCreator with Gson for this type may fix this
problem.
I'm not sure about proper way to handle such trivial case. Any links, code examples or help appreciated. Thanks in advance.
Update
Thanks to Niklas I reconsidered gson into such structure:
lateinit var gson: Gson
when (methodName) {
RequestList.LOGIN.methodName -> {
gson =
GsonBuilder().registerTypeAdapter(
GeneralResponseModel::class.java,
object : JsonDeserializer<GeneralResponseModel> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): GeneralResponseModel {
val gsonInner = Gson()
val jsonObject: JsonObject = json!!.asJsonObject
lateinit var generalResponseModel: GeneralResponseModel
generalResponseModel = if (!jsonObject.get("success").asBoolean) {
GeneralResponseModel(
false,
gsonInner.fromJson(jsonObject.get("data"), ResponseError::class.java)
)
} else {
GeneralResponseModel(
true,
gsonInner.fromJson(jsonObject.get("data"), DriverData::class.java)
)
}
return generalResponseModel
}
}).create()
}
RequestList.GET_JOBS.methodName -> {
gson = GsonBuilder().registerTypeAdapter(
GeneralResponseModel::class.java,
object : JsonDeserializer<GeneralResponseModel> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): GeneralResponseModel {
val gsonInner = Gson()
val jsonObject: JsonObject = json!!.asJsonObject
lateinit var generalResponseModel: GeneralResponseModel
generalResponseModel = if (!jsonObject.get("success").asBoolean) {
GeneralResponseModel(
false,
gsonInner.fromJson(jsonObject.get("data"), ResponseError::class.java)
)
} else {
GeneralResponseModel(
true,
gsonInner.fromJson(jsonObject.get("data"), Array<JobResponse>::class.java)
)
}
return generalResponseModel
}
}).create()
}
else -> gson = Gson()
}
Since your data is very generic, it can't really be parsed in a type-safe way. Gson can't infer types based on pure text (in your specific case there is nothing telling Gson that your data is a ResponseError).
I would consider a general wrapper class like yours, and then using a GSON TypeAdapter to parse the response to your general wrapper.
You have to instantiate GSON with a Builder to define a custom TypeAdapter.
registerTypeAdapter(Type type, Object typeAdapter)
Configures Gson for custom serialization or deserialization.
Your wrapper:
public class Response<T> {
T data;
String message;
public Response(T data, String message) {
this.data = data;
this.message = message;
}
boolean hasData() {
return data != null;
}
T getData() {
return data;
}
String getMessage() {
return message;
}
}
Initialization:
GsonBuilder builder = new GsonBuilder();
b.registerTypeAdapter(Response.class, new JsonDeserializer<Response>() {
#Override
public Response deserialize(JsonElement arg0, Type arg1,
JsonDeserializationContext arg2) throws JsonParseException {
// ... create Response object here
return response;
}

How to properly make reusable class to catch response data using retrofit?

I am new in Android development, and I am trying to get data from server. the general JSON response structure will be like this
{
"success": "1",
"data": [
{
"customers_id": 4,
"customers_gender": "0",
"customers_firstname": "TES IOS",
"customers_lastname": "TES IOS",
"customers_dob": "2018-12-27",
"email": "TES002#email.com",
"user_name": "TES002",
"customers_default_address_id": 0,
"customers_telephone
},
"message": "Successfully get user data from server"
}
the "success" and "message" field will be the same (will always be string). but the "data" can be different for other request call. It can send user data, store data or product data, or even Array/List of Products.
so I want to make general reusable class to catch that JSON response. the class will be like this, I set the "data" to be Any, and then later it will be casted back to User object:
class ServerData(successStatus: Int, data: Any, message: String) {
val isSuccessfull : Boolean
val data : Any
val message : String
init {
isSuccessfull = successStatus != 0
this.data = data
this.message = message
}
}
the interface is like this:
interface LakuinAPI {
#FormUrlEncoded
#POST("processlogin")
fun performLogin(
#Field("kode_customer") outletCode: String,
#Field("password") password: String
): Call<ServerData>
}
and then I use it in the activity, like the code below:
private fun sendLoginDataToServer(outletCode: String, password: String) {
val call = lakuinAPI.performLogin(outletCode,password)
call.enqueue(object: Callback<ServerData> {
override fun onFailure(call: Call<ServerData>, t: Throwable) {
Toast.makeText(this#LoginActivity,t.localizedMessage,Toast.LENGTH_LONG).show()
}
override fun onResponse(call: Call<ServerData>, response: Response<ServerData>) {
if (!response.isSuccessful) {
Toast.makeText(this#LoginActivity,"Code: " + response.code(),Toast.LENGTH_LONG).show()
return
}
val lakuinServerData = response.body()
val userList = lakuinServerData?.data as List<User> // the error in here
val userData = userList.first() // the error in here
println(userData)
}
})
}
but I get error message:
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap
cannot be cast to com.ssss.lakuinkotlin.Model.User
I give comment in the code above the location of the error. I don't why it happened.
to be honest, I am not if this is the correct way to catch user data from general response JSON like the the JSON above. is there a better way ?
You can use generics to achieve it
class Response<Data> constructor() : ResponseSimple() {
#SerializedName(FIELD_DATA)
var data: Data? = null
private constructor(data: Data) : this() {
this.data = data
}
companion object {
const val FIELD_SUCCESS = "success"
const val FIELD_ERROR = "error"
const val FIELD_DATA = "data"
const val FIELD_MESSAGE = "message"
#JvmStatic
fun <Data> create(data: Data): Response<Data> {
return Response(data)
}
}
}
And ResponseSimple is
open class ResponseSimple {
#SerializedName(Response.FIELD_ERROR)
var error: String = ""
#SerializedName(Response.FIELD_SUCCESS)
var succes: Boolean = false
#SerializedName(Response.FIELD_MESSAGE)
var message:String = ""
}
Then api response should be Call<Response<ServerData>>.
And about ClassCastException, you can't convert ServerData to User just using as.
You need to use Call<Response<ArrayList<User>>> or create class converter.
Try replacing this line :
val userList = lakuinServerData?.data as List<User>
with:
val userList = lakuinServerData?.data as new TypeToken<List<User>>(){}.getType()

Categories

Resources