Kotlin: Deserialize JSON to a different SerializedName - android

I'm migrating a Kotlin code from using a backend to use a new one.
I'm having an issue in the next situation:
The old backend would return a JSON response-body with next schema:
{code: "...", message: "...", data: "{"id": ..., "name": ..., ...}"}.
But the new backend returns only
If not-success: {code: "...", message: "..."}.
If success or {"id": "...", "name": "...", ...}
This should be mapped to a class ResponseBody.
data class ResponseBody<T>(
#SerializedName(value: "code") val responseCode: Int,
#SerializedName(value: "message") val message: Int,
#SerializedName(value: "data") val data: T?)
)
Which is inside a class Data Wrapper:
data class DataWrapper<T>(
val responseBody: ResponseBody<T>?,
val throwable: Throwable?)
)
I have a method execute() where data mapping should happen after receiving response from backend.
fun <T> execute(observable: Single<ResponseBody<T>>): Single<DataWrapper<T>> {
return observable
.subscribeOn(Schedulers.io())
.map {t -> DataWrapper(t, null)}
.onErrorReturn {t -> DataWrapper(null, t)}
.map {
(...)
}
}
So, my problem is that I'm not able to serialize in the case of success the response-body I get
{"id": "...", "name": "...", ...}
to the 'data' property in ResponseBody. I know that the problem is the fact that that response-body is not encapsulated in a {"data": ...} like it was in the old backend so the serialization doesn't work. Is there a dirty fix for this? Any suggestion?
I want to get, in the case of success, the response-body to be saved in the 'data' property of the ResponseBody class.
Thank you in advance!

Related

Android- Json object come as object or array

I have this json response from the api, and the response can't be change
data class Weather (
val category: String,
val id: String,
val meta: Meta
)
data class Meta (
val id: String,
val name: String,
val details: String
)
Json respose
{
"weather" : {
"category": "articles",
"id": "1",
"meta": {
"id": "1",
"name": "The shortest article. Ever.",
"details": "see"
},
"weather" : {
"category": "articles",
"id": "2",
"meta": []
}
If meta is empty, it come with an array but if not empty, it come with object.
Retrofit throws
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY
the api can't be modify so this has to be fix on client end. How can I solve this
You can not make this possible
meta object must be an object when have value and null when do not have any value, or an array with value when exist and empty when not exist.
meta can not be an array and object in the same time.
this is very bad mistake from who created this response body.
You can use Any type for meta. and put check at your code level like this.
data class Weather (
val category: String,
val id: String,
val meta: Any
)
if(meta is Meta)
parse it to your Meta object
else
parse it to list

how call data from json like this with Retrofit library

i tried this :
interface MYAPI {
#GET("get-languages")
fun getdata() : Call<List<Data.Language>>
}
this is my api service
{
"message": "success",
"data": {
"language": [ {"id": 5,
"name": "English",
"icon": "19638193-en.png"
},
{
"id": 6,"name": "turkish","icon": "19638199-tr.png"}
]
}
}
{
"message":"success",
"data":{
"language":[]
}
}
Share your response model but you will need the "data" attribute that is a language model. If you use directly the language model then won´t works.
So a possible data could be:
data class LanguageResponse(val id: Int, val name: String, val icon: String)
data class LanguagesResponse(val language: List<LanguageResponse>)
data class DataLanguageResponse(val data: LanguagesResponse)
And your call:
interface MYAPI {
#GET("get-languages")
fun getdata() : Call<DataLanguageResponse>
}
Solved..
i add to AndroidManifest :
android:usesCleartextTraffic="true"

Consuming polymorphic json "data: { put_anything_here }" with Gson & Retrofit

I'm not sure if polymorphic is the right term to use so my apologies.
I'm working with the following API:
Request body:
{
"user_id": "user_id",
"command": "submit_document",
}
Response:
{
"result": "success",
"code": 200,
"status": "ok",
"screen": "documents_rejected", // This is unique for different `data`
"next_screen": "",
"message": "Successful",
"data": {
// `data` is always a json object with known fields and parameters
}
}
I have data classes ready for different types of data responses like:
data class PhoneData(
#SerializedName("phone_number")
val phoneNumber: String? = null,
#SerializedName("phone_status")
val phoneStatus: String? = null
)
for "screen": "phone" and the following for another screen:
data class Data(
val deepLink: String? = null
)
The problem is, at the start, I have to make the following request to retrieve the current screen:
{
"user_id": "user_id",
"command": "get_current_screen",
}
which returns a similar response as above:
{
"result": "success",
"code": 200,
"status": "ok",
"screen": "main_screen", // Different types of screen types are known.
"next_screen": "",
"message": "Successful",
"data": {
// `data` is always a json object but the object could contain anything depending on the `screen` type.
}
}
but the data field could contain anything depending on the screen
data class SplashScreenData(
// How do I make this data class combine all other data classes? One ugly approach is to add all the fields from different `data` classes here and use this one only.
)
I found about the RuntimeTypeAdapterFactory for polymorphic cases but am not sure how to make it work when there's no "type" like field within the data object (screen is unique but it's outside the data object).
It would be very helpful if someone has a solution or could point me in a direction.
val frameTextReceived: String = frame.readText()
val jsonObject = JsonParser.parseString(frameTextReceived).asJsonObject
val type = when (jsonObject.get("type").asString) {
TYPE_JOIN_ROOM -> JoinRoom::class.java
TYPE_GAME_MOVE -> GameMove::class.java
TYPE_DISCONNECT_REQUEST -> DisconnectRequest::class.java
else -> BaseModel::class.java
}
val payload = gson.fromJson(frameTextReceived, type)
This is my solution, here I have type parameter by which I can know in which class I have to deserialize the object but in your case you have screen parameter, you can use this.

Convert JSON to POJO with different data type

I have this response, i have problem when i want to convert to pojo.
"equity": {
"0": {
"name": [
"Abc"
],
"code": [
"3410"
],
"ending_balance": [
301834470
]
},
"1": {
"name": [
"Xyz"
],
"code": [
"2180"
],
"ending_balance": [
0
]
},
"2": {
"name": [
"Pqr"
],
"code": [
"9220"
],
"ending_balance": [
0
]
},
"total_equity": 301834470
}
}
I'm confused about giving the right data type, because there are arrays("0","1","2") that contain objects and "total_equity" that contain number.
I've tried to give the map data type, but it will be error for "total_equity"
var equity: Map<String, EquityDto?>
If you know the solution for this problem please help me. Thank you
You can use
var equity: Map<String, Any>
While accessing the Any Data type u can compare the varrriable type(instance of) and use the value as following
if (valueofMap is Int){
//here is integer value
}
if (valueofMap is yourDataClass){
//your custom class
}
There can be a solution to this, but it will be a lengthy one.
One solution can be to transform the response to Map<String, Any> then you will have to check the type every time you have to use it and it can really annoying when you are using it in multiple classes.
Another solution can be to create a custom Custom Type Adapter which you can pass to the Retrofit Instance in the addConverterFactory method.
To create a custom adapter, you just have to follow the following steps:
Create a model in which you want to store the data. In your case it can be :
data class ApiResponse(
val data: Map<String, EquityDto?>,
val totalEquity:Int
)
Create the Adapter:
class StudentAdapter : TypeAdapter<ApiResponse?>() {
#Throws(IOException::class)
fun read(reader: JsonReader): ApiResponse {
val student:ApiResponse? = null
reader.beginObject()
var fieldname: String? = null
while (reader.hasNext()) {
var token = reader.peek()
if (token == JsonToken.NAME) {
//get the current token
fieldname = reader.nextName()
}
if ("total_equity" == fieldname) {
token = reader.peek()
student?.totalEquity = reader.nextInt()
}else {
token = reader.peek()
student?.data?.set(fieldname.toString(), reader.nextString())
}
}
reader.endObject()
return student
}
#Throws(IOException::class)
fun write(writer: JsonWriter, student: ApiResponse) {
writer.beginObject()
writer.name("data")
writer.value(student.data.toString())
writer.name("totalEquity")
writer.value(student.totalEquity)
writer.endObject()
}
}
If you know a better way to create a type adapter then you can surely use that.

Can't convert json response using gson

When I start request using retrofit 2.0, I'm getting this error com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $
and don't know why, becoause I have a valid json.
I have also seen this kind of error but didn't helped, I don't want to use JsonReader and this kind of stuff
P.S I'm using Gson for converter
[
{
"fullname": "name",
"contact": "blah",
"uid": "001"
},
{
"fullname": "name2",
"contact": "blah2",
"uid": "002"
}
]
this is my call interface method
#FormUrlEncoded
#POST("url")
fun startRequest(#Field("someField") someField: String) : Call<List<MyModelExample>>
and this is my response MyModelExample class
open class MyModelExample : RealmObject() {
#SerializedName("fullname")
var fullName: String? = null
#SerializedName("contact")
var contact: String? = null
#PrimaryKey
#SerializedName("uid")
var uid: String = ""
}

Categories

Resources