how to handle two different Retrofit response in Kotlin? - android

I have tried to read this similar thread in java in here
so I try the accepted answer there but it doesn't work. here is my problem
I will get two different JSON Response from an endpoint. if I successfully get the Restaurant data, the JSON will be like this
{
"R": {
"has_menu_status": {
"delivery": -1,
"takeaway": -1
},
"res_id": 18941862,
"is_grocery_store": false
},
"id": "18941862",
"name": "Pizza Maru",
"url": "https://www.zomato.com/jakarta/pizza-maru-1-thamrin?utm_source=api_basic_user&utm_medium=api&utm_campaign=v2.1",
"location": {
"address": "Grand Indonesia Mall, East Mall, Lantai 3A, Jl. M.H. Thamrin No. 1, Thamrin, Jakarta",
"locality": "Grand Indonesia Mall, Thamrin",
"city": "Jakarta",
"city_id": 74,
"latitude": "-6.1955810000",
"longitude": "106.8213770000",
"zipcode": "",
"country_id": 94,
"locality_verbose": "Grand Indonesia Mall, Thamrin, Jakarta"
},
"switch_to_order_menu": 0,
"cuisines": "Pizza",
"timings": "10 AM to 10 PM",
"average_cost_for_two": 180000,
"price_range": 3,
"currency": "IDR",
"thumb": "https://b.zmtcdn.com/data/pictures/chains/2/18941862/403aa36cb046e86a694e7989bb7cd545.jpg?fit=around%7C200%3A200&crop=200%3A200%3B%2A%2C%2A",
"has_online_delivery": 0,
"is_delivering_now": 0,
"store_type": "",
"phone_numbers": "021 3108656",
}
then If I send invalid restaurantID then I will get error JSON Response like this:
{
"code": 404,
"status": "Not Found",
"message": "Not Found"
}
here is the data class I made
data class Restaurant (
#SerializedName("id")
val id : Int = 0,
#SerializedName("name")
var name : String = "",
#SerializedName("url")
val url : String = "",
#SerializedName("location")
val location : Location = Location(),
#SerializedName("currency")
val currency : String = "",
#SerializedName("phone_numbers")
val phone_numbers : String = "",
#SerializedName("thumb")
val thumbnail : String = ""
)
for successful Response
data class Location (
#SerializedName("address")
val address : String = "",
#SerializedName("city")
val city : String = "",
#SerializedName("latitude")
val latitude : Double = 0.0,
#SerializedName("longitude")
val longitude : Double = 0.0,
#SerializedName("zipcode")
val zipcode : String = ""
)
for Error Response
data class ErrorResponse (
val code : Int,
val status : String,
val message : String
)
here is my Interface for my Retrofit. the idea is, I will cast it as Any first, then I will downcast either to Restaurant or ZomatoErrorResponse
interface RestaurantAPI {
#Headers("user-key: $USER_KEY_ZOMATO")
#GET("restaurant")
fun getRestaurantDetail(
#Query("res_id") id: Int
): Call<Any>
}
here is the error:
so I use my retrofit like this
val call = restaurantService.getRestaurantDetail(restaurantID)
call.enqueue(object: Callback<Any>{
override fun onResponse(call: Call<Any>, response: Response<Any>) {
if (response.isSuccessful) {
// this line is executed
Log.d("checkxxx","${response.body()}")
val restaurantData = response.body() as Restaurant // <-- but Error while casting Any to Restaurant in here
restaurant.postValue(restaurantData)
}
}
})
my app crash at that line. but actually I can successfully get the data, but I fail to cast it to Restaurant.
here the logcat of my response.body()
what went wrong in here ?
or maybe there is a better approach than this one

I finally can solve my problem using this code below
val call = restaurantService.getRestaurantDetail(restaurantID)
call.enqueue(object: Callback<Any>{
override fun onResponse(call: Call<Any>, response: Response<Any>) {
if (response.isSuccessful) {
val gson = Gson()
val restaurantData = gson.fromJson(gson.toJson(response.body()), Restaurant::class.java)
} else {
val errorBody = response.errorBody() ?: return
val type = object : TypeToken<ErrorResponse>() {}.type
val errorResponse: ErrorResponse? = gson.fromJson(errorBody.charStream(), type)
val errorMessage = errorResponse?.message ?: "Unknown Error"
}
}
})
don't forget to set the interface to be Any like this
interface RestaurantAPI {
#Headers("user-key: $USER_KEY_ZOMATO")
#GET("restaurant")
fun getRestaurantDetail(
#Query("res_id") id: Int
): Call<Any> // <---- set to Any like this
}
in my case, I have successful response and an error response. so I need to separate it like that.
but if you have 2 successful responses but it has different JSON then you need to perform null checking to restaurantData in my code above, if null then mapping it the other POJO.

You should use gson to convert json to an object
https://github.com/google/gson
Example
val gson = Gson();
val jsonInString = "{\"userId\":\"1\",\"userName\":\"Yasir\"}";
val user = gson.fromJson(jsonInString, User.class);

Related

Kotlin | Jackson annotation | How to fix Out of START_ARRAY token Error

Can anybody say where I am doing wrong. I have json like that
[
{
"id": "1",
"name": "ff",
"surname": "ggg",
"cap": "10000"
},
{
"id": "1",
"name": "aaa",
"surname": "hhh",
"cap": "22222"
},
{
"id": "1",
"name": "rrr",
"surname": "hhhhhdr",
"cap": "33333"
},
{
"id": "1",
"name": "hhh",
"surname": "qqqqq",
"cap": "44444"
}
]
And I parse to this class.
data class ResponseList(
val capList: List<Response>?
) {
data class Response(
#JsonProperty("id")
val id: String,
#JsonProperty("name")
val name: String,
#JsonProperty("surname")
val surname: String,
#JsonProperty("cap")
val cap: String
)
}
When I try to parse it the list is always null and if I try to test it I have this error:
Cannot deserialize value of type com.myapp.ResponseList from Array value (token JsonToken.START_ARRAY)
just class Response is needed, like following:
fun test(){
val jsonStr = "your json str"
val mapper = ObjectMapper()
val lendReco: List<Response> =
mapper.readValue(jsonStr, object : TypeReference<List<Response?>?>() {})
}
data class Response(
#JsonProperty("id")
val id: String,
#JsonProperty("name")
val name: String,
#JsonProperty("surname")
val surname: String,
#JsonProperty("cap")
val cap: String
)

How to select specific part of JSON and convert it to a List in retrofit with Moshi

I'm getting the JSON blow from an API with retrofit and I want to only select the production_companies array from it and convert it to a list of ProductionCompanie class, how I can do it with Moshi without using nested classes?
{
"backdrop_path": "/52AfXWuXCHn3UjD17rBruA9f5qb.jpg",
"belongs_to_collection": null,
"budget": 63000000,
"genres": [
{
"id": 18,
"name": "Drama"
}
],
"homepage": "http://www.foxmovies.com/movies/fight-club",
"id": 550,
"popularity": 40.054,
"poster_path": "/8kNruSfhk5IoE4eZOc4UpvDn6tq.jpg",
"production_companies": [
{
"id": 508,
"logo_path": "/7PzJdsLGlR7oW4J0J5Xcd0pHGRg.png",
"name": "Regency Enterprises",
"origin_country": "US"
},
{
"id": 711,
"logo_path": "/tEiIH5QesdheJmDAqQwvtN60727.png",
"name": "Fox 2000 Pictures",
"origin_country": "US"
},
{
"id": 20555,
"logo_path": "/hD8yEGUBlHOcfHYbujp71vD8gZp.png",
"name": "Taurus Film",
"origin_country": "DE"
},
{
"id": 54051,
"logo_path": null,
"name": "Atman Entertainment",
"origin_country": ""
}
],
"vote_count": 21181
}
this is my retrofit Apis interface:
interface Apis {
#Headers("Content-Type: application/json")
#GET("/3/movie/550")
fun getData(#Query("api_key") key: String = apiKey): Call<List<ProductionCompanie>>
}
and my model:
#JsonClass(generateAdapter = true)
data class ProductionCompanie(
#Json(name = "id")
val id: Int,
#Json(name = "logo_path")
val picture: String,
#Json(name = "name")
val name: String
)
I ended up using a custom adapter:
class ProductionCompanieListAdapter(private val moshi: Moshi) {
#FromJson
fun fromJson(value: JsonReader): List<ProductionCompanie>? {
val json = JSONObject(value.nextSource().readUtf8())
val jsonArray = json.getJSONArray("production_companies")
val type = Types.newParameterizedType(List::class.java, ProductionCompanie::class.java)
val adapter = moshi.adapter<List<ProductionCompanie>>(type)
return adapter.fromJson(jsonArray.toString())
}
#ToJson
fun toJson(value: List<ProductionCompanie>): String {
val type = Types.newParameterizedType(List::class.java, ProductionCompanie::class.java)
val adapter = moshi.adapter<List<ProductionCompanie>>(type)
return adapter.toJson(value)
}
}

Expected BEGIN_OBJECT but was NUMBER at line 1 column 40 path $.result.loggedUser

I'm trying to consume an API in kotlin and this is the first time I'm working with API's and especially with retrofit, I checked online for a solution but I couldn't understand how to go about with this.
this is the API link
https://connect.managedcoder.com/api/leaves
inorder to get the response I have to pass a token as part of the parameter so the link has to be :
https://connect.managedcoder.com/api/leaves?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6InNoYW5lLnBlcmVpcmFAc2ppbm5vdmF0aW9uLmNvbSIsInBhc3N3b3JkIjoicGFzc3dvcmQxMjMifQ.ncwng1CK8Y2N4z7ZofgB94ZVxJ8V5L8fk1JKGLnNr2s
however, the API fails and it returns the above error and goes into the on failure method.
I'm not sure if the way I have constructed the URL while calling the API is correct and if that is the cause of the issue, or it is due to some other reason.
This is my response to the API :
{
"status": "OK",
"result": {
"loggedUser": {
"id": 119,
"first_name": "Shane",
"last_name": "Pereira",
"emp_id": "Goa6",
"personal_email": "shanepereira30#gmail.com",
"office_email": "shane.pereira#sjinnovation.com",
"present_address": "Hno. 172 Church Street Cortalim Goa 403710",
"permanent_address": "Hno. 172 Church Street Cortalim Goa 403710",
"mobile_number": "2147483647",
"alternate_number": "2147483647",
"emergency_number": "2147483647",
"country": "India",
"office_location": "GOA",
"gender": 1,
"birth_date": "1970-01-01T00:00:00",
"maritial_status": null,
"identity_proof": "",
"blood_group": "",
"bank_name": "",
"bank_account_number": "",
"salary": null,
"tax_bracket": "",
"languages": "",
"max_qualification": "--",
"designation_id": 33,
"shift_type": "09:00 - 18:00",
"department_id": null,
"reporting_team": "",
"reporting_manager": "",
"reporting_manager_responsibilities": "0",
"mentor": "",
"date_of_joining": "2019-01-03T00:00:00",
"source_of_hire": "",
"referred_by": "",
"employment_status": "1",
"work_phone": "8390429861",
"employment_type": "full-time",
"confirmation_date": "2019-06-03T00:00:00",
"increment_date": "1970-01-01T00:00:00",
"resignation_date": "1970-01-01T00:00:00",
"last_working_date": "1970-01-01T00:00:00",
"notice_period": "2 Months ",
"reason": "",
"blacklisted": "",
"notes": "",
"knowledge": "--",
"role_id": 4,
"is_manager": 0,
"created": "2019-03-18T11:40:48",
"modified": "2019-03-18T11:40:48",
"profile_pic": "",
"is_pm": 0,
"hubstaff_name": null
},
"error": "No Leave days has been assigned. Please contact with HR.",
"success": false
}
}
this is my get request
#GET("api/leaves?")
fun getLeaveData(
#Query("token")token:String
):Call<LoginResponse>
this is my retrofit instance :
object RetrofitClient {
private val AUTH = "Basic"
private const val BASE_URL = "https://connect.managedcoder.com/"
private val okHttpClient = OkHttpClient.Builder()
.addInterceptor{chain ->
val original = chain.request()
val RequestBuilder = original.newBuilder()
.addHeader("Authorization", "")
.method(original.method(),original.body())
val request = RequestBuilder.build()
chain.proceed(request)
}.build()
val instance: Api by lazy {
val retrofit= Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
retrofit.create(Api::class.java)
}
}
and I have 3 classes which are LoginResponse, Result, and User
the LoginResponse contains the result and status of the response, the result contains the token, success and holds the logged user details from the user class which contains the id, name, and email
Login response:
data class LoginResponse ( val status: String, val result:Result) {
}
Result:
data class Result (val token:String, val success: String,val loggedUser: User){
}
User:
data class User(val id:Int?, val office_email: String?, val first_name: String?)
and here is where I call the API
if(token!=""){
RetrofitClient.instance.getLeaveData(token)
.enqueue(object :retrofit2.Callback<LoginResponse> {
override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
Toast.makeText(context,"error"+t.message, Toast.LENGTH_LONG).show()
Log.d("xxerror response xxxxx", t.message)
}
override fun onResponse(
call: Call<LoginResponse>,
response: Response<LoginResponse>
) {
// Toast.makeText(context,response.body()?.result?.success, Toast.LENGTH_LONG).show()
if (response.body()?.status=="OK"){
Toast.makeText(context,"mellooooo"+response.body()?.result?.loggedUser?.first_name, Toast.LENGTH_LONG).show()
}else{
Toast.makeText(context,"api failed to load"+response.body()?.status,
Toast.LENGTH_LONG).show()
}
}
})
}else{
Toast.makeText(context,"token is empty",
Toast.LENGTH_LONG).show()
val i= Intent(activity, LoginActivity::class.java)
startActivity(i)
SharedPrefManager.getInstance(this.requireContext()).clear()
}
For the GET request provided above, below is the response.
{"status":"OK","result":{"loggedUser":0,"error":"No Leave days has been assigned. Please contact with HR.","success":false}}
You can see that loggedUser is 0 instead of null
When the success=false the User Model expected is JsonObject and but response is an Int thus an error. Reason why it does not fall into onError block because it's not an HTTP error but a business logic error.
You can manually serialise the object based on success using Gson or ask the server to send null instead of 0

fetching value of specific key from json response

how can I store a specific value of a key from json response into a variable
{
"results": [
{
"name": ryan,
"roll_id": 64,
"class_id": 310,
"net_id": 95,
},
],
};
above is the json response :-
val gson = GsonBuilder().create()
val ListV = gson.fromJson(body, HomeClass::class.java)
after these 2 lines I'm totally clueless how to do it I've gone through Internet but It was hard for me to understand how to proceed further.
Your Json Structure will be
{
"results": [
{
"name": "Amiyo",
"roll_id": 1,
"class_id": 10,
"net_id": 91
},
{
....
}
]
}
Data class should be
data class HomeClass (
#SerializedName("results") val results : List<Results>
)
data class Results (
#SerializedName("name") val name : String,
#SerializedName("roll_id") val roll_id : Int,
#SerializedName("class_id") val class_id : Int,
#SerializedName("net_id") val net_id : Int
)
fromJson
val listData = gson.fromJson(jsonData, HomeClass::class.java)
Then
val totalSize = 0 until listData!!.size
if(totalSize.size>0)
{
for (i in totalSize)
{
//Your Code i==Position
}
}

Parsing API data which contain object (Klaxon) (Kotlin)

I have API response which contain object (graphic)
[
{
"code": 200,
"status": "OK",
"FirstDay": "2019-11-18",
"LastDay": "2019-11-24",
"graphic": {
"2019-11-23": [
{
"godzinaStart": "08:30",
"godzinaStop": "10:00",
"przedmiot": "Matematyka dyskretna",
"sala": "32AK8",
"nauczyciel": "xxx",
"grupy": "1K131; 1K132; 1K133; 1K134; 1K135; 2K131",
"typ": "wykład"
},
],
"2019-11-24": [
{
"godzinaStart": "08:30",
"godzinaStop": "10:00",
"przedmiot": "Podstawy informatyki",
"sala": "308K",
"nauczyciel": "xxx",
"grupy": "1K131",
"typ": "laboratorium"
},
]
}
}
]
I have to parse this JSON to object in Kotlin. So i made class with parameters
class GraphicAPIResponse(
var code: Int,
var status: String,
var errorMessage: String = "",
var FirstDay: String = "",
var LastDay: String = "",
var graphic: JsonObject? = null OR var graphic: JsonArray<Any>? = null (I tried both)
)
I'm parsing data by this function
val responeAPI = Klaxon().parseArray<GraphicAPIResponse>(response)
When graphic is JsonObiect type appliaction throw error
I/System.out: ERROR -> Unable to instantiate JsonObject with parameters []
When graphic is JsonArray<Any> type, here's error
I/System.out: ERROR -> Unable to instantiate GraphicAPIResponse with parameters [LastDay: 2019-11-24, code: 200, status: OK, graphic: java.lang.Object#aef265a, FirstDay: 2019-11-18]
I'm trying to resolve the problem from 2 hours. Can someone help me please? :(
#EDIT
Thank You #Alexey Romanov
That help
Define a type for the nested object:
class Lesson(val godzinaStart: String, val godzinaStop: String, ...)
and use it in GraphicAPIResponse:
class GraphicAPIResponse(
var code: Int,
var status: String,
var errorMessage: String = "",
var FirstDay: String = "",
var LastDay: String = "",
var graphic: Map<String, Lesson> = mapOf()
)
(though honestly, I'd expect JsonObject to work as well)

Categories

Resources