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.
Related
Please show me a way to make a proper model for this JSON so that I get "Indian Premier League" as the key and the array next to it as value. We can have multiple leagues as well in the json.
{
"keySeriesNews": {
"Indian Premier League": [
{
"id": 203,
"slug": "tata-ipl-2022-why-delhi-capitals-bought-shardul-thakur-for-inr-1075-crore",
"competition_id": 3269,
"image_id": 1203,
"image_caption": "Shardul Thakur in action",
"start_date": "2022-03-05 17:25:38",
"created_at": "2022-03-05 12:08:19",
"updated_at": "2022-04-15 06:50:30",
"headline": "TATA IPL 2022: Why Delhi Capitals bought Shardul Thakur for INR 10.75 crore",
"sport_id": 15,
"image": {
"id": 1203,
"file_name": "shardulthakur_new.webp",
"created_at": "2022-04-15 06:47:41",
"image_path": "https://stagingkisma.6lgx.com/storage/images/shardulthakur_new_320x320.webp"
},
"competition": {
"id": 3269,
"slug": "indian-premier-league-2",
"competition_name": "Indian Premier League"
}
}
]
}
}
I have used this model to parse in retrofit but it is not fetching any data from the API. It is completely blank. No data in it.
data class HomeNewsParentModel(
#SerializedName("keySeriesNews" ) var keySeriesNews: JSONObject? = JSONObject()
)
However, when I use this model, it fetches data and I can access it. But problem is that it is hardcoded. I mean, these models will not capture data if the league name changes in any case. Here are the models which captured data.
data class HomeNewsParentModel(
#SerializedName("keySeriesNews" ) var keySeriesNews: KeySeriesNews? = KeySeriesNews()
)
data class KeySeriesNews (
#SerializedName("Indian Premier League" ) var league : ArrayList<League> = arrayListOf()
)
data class League (
#SerializedName("id" ) var id : Int? = null,
#SerializedName("slug" ) var slug : String? = null,
#SerializedName("competition_id" ) var competitionId : Int? = null,
#SerializedName("image_id" ) var imageId : Int? = null,
#SerializedName("image_caption" ) var imageCaption : String? = null,
#SerializedName("start_date" ) var startDate : String? = null,
#SerializedName("created_at" ) var createdAt : String? = null,
#SerializedName("updated_at" ) var updatedAt : String? = null,
#SerializedName("headline" ) var headline : String? = null,
#SerializedName("sport_id" ) var sportId : Int? = null,
#SerializedName("image" ) var image : Image? = Image(),
#SerializedName("competition" ) var competition : Competition? = Competition()
)
I have coded for a parser on the generic side to handle key-value type JSON like this but the JSON object was empty when I used the first approach of the data model. I need to make a generic parser to fetch league names as well as their data in key-value format since there can be multiple leagues that can come in this response as well.
PS: This is my parser which is getting empty JSON Object
private fun parseJSONData(data: JSONObject){
try {
val jsonObject = JSONObject(data)
for (key in jsonObject.keys()) {
Toast.makeText(
this#SeriesFragment.requireContext(),
"Key : " + key + " Value: " + jsonObject.optString(key),
Toast.LENGTH_SHORT
).show()
}
} catch (e: JSONException) {
e.printStackTrace()
}
}
Your help is much appreciated. Thanks.
Just a tip - if you already have the JSON available, you can use this plugin to easily generate a first draft of your model and adapt it if needed.
Some questions:
If you can have multiple leagues in your response, shouldn't keySeriesNews also be a list and not just a JSON object? For example like this:
{
"keySeriesNews": [
{
"id": 203,
"title": "Indian Premier League",
"slug": "tata-ipl-2022-why-delhi-capitals-bought-shardul-thakur-for-inr-1075-crore",
"competition_id": 3269,
"image_id": 1203,
...
}
]
}
What's your reasoning for handling JSON manually instead of using a ConverterFactory?
Where and how are you calling parseJsonData?
Well, I am not sure about this is correct or not. If anyone has a standard way of doing it, it is much appreciated. However, I have used the JSONElement instead of JSONObject or JSONArray and have used Map to handle key-value type data in my model, and GSONConvertorFactory has got this one right and fetched data correctly. This is the model I used:
data class HomeNewsParentModel(
#SerializedName("keySeriesNews" ) var keySeriesNews: HashMap<String, JsonElement>? = HashMap()
)
And I will parse JSONElement in my parseJsonData function to handle the key-value of this nonstandard JSON coming from API.
Hope this helped you in some way.
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.
So I made an api in laravel and it returns a response like this:
{
"message": "The given data was invalid.",
"errors": {
"email": [
"The email has already been taken."
],
"mobile": [
"The mobile has already been taken."
]
}
}
Can somebody show me how to get the specific values from errors?
You may create model representing your error json and use Gson to parse it. Here is some short example.
data class Errors(
val email: List<String>,
val phone: List<String>
)
data class YourErrorModel(
val message: String,
val errors: Errors
)
fun parseError(response: Response<*>): YourErrorModel? {
val errorBody = response.errorBody()?.string() ?: return null //No error body present
return Gson().fromJson(errorBody, YourErrorModel::class.java)
}
Also don't forget to handle nullable types in your response. And i suggest you to return just string, not array if that is exact error for field.
How about this :
JSONObject errorObject = yourJSONObject.optJSONObject("errors");
if (errorObject != null){
JSONArray emailMsgArray = errorObject.getJSONArray("email");
JSONArray mobileMsgArray = errorObject.getJSONArray("mobile");
String emailMsg= emailMsgArray.getString(0);
String mobileMsg= mobileMsgArray .getString(0);
}
I have a json string with 2 keys error and user. First I want to check if error is not false and get the values from user.
Here is the Json String:
{
"error": false,
"user": {
"id": 26,
"name": "Someone",
"email": "someone#gmail.com",
"aktif": 1
}
}
How can I achieve this ?
Get the JsonObject "error" first :
val errorCheck = yourjsonresult.getJSONObject("error");
Then compare to check if it was false then:
if(errorCheck.equals("false")) { // or if it wasn't false -> !errorCheck.equals("false"))
val data = yourjsonresult.getJsonObject("user"); // get the user object
val name = data?.getString("name"); // or the other items
}
The result should be:
Someone
Also, arrays starts by [ but in your case, those are json objects which starts-ends by {}.
Similarly to this question, I would like to convert an object (actually, it is a API response from retrofit) to a json string, so it would be simpler to store it somewhere.
The response structure is something like these:
{
"metadata": {
"count": 0,
"type": "string"
},
"results": [
{
"obj1": {
"param1": "s1",
"param2": "s2"
},
"obj2": {
"param3": 0,
"param4": 0,
"param5": 0
},
"obj3": 0,
"obj4": "27/12/2017"
}
]
}
Using retrofit2, I have the results array stored in a List<MyResponse.Result> and that's the parameter I'm passing to Gson().toJson, like so:
var contentResponse: String = ""
try{
this.contentResponse.plus(Gson().toJson(response))
} catch (e: Exception){
Log.e("Gson error", e.toString())
}
Unfortunately, I'm getting no exception but my contentResponse keeps empty. I`ve tried to use the method in the question mentioned above, but got the same outcome. Any advises?
PS: If there is an easier way to get the retrofit response in a String, it could help as well.
Strings are immutable in JVM. Calling
this.contentResponse.plus(Gson().toJson(response))
is equivalent to
this.contentResponse + (Gson().toJson(response))
This way you can see better that you are not assiging the result to anything. Change it to
this.contentResponse = this.contentResponse.plus(Gson().toJson(response))