How can I define dynamic data class? (Android - Retrofit2) - android

I am developing an Android App via Kotlin. The app uses Retrofit2 for HTTP requests. I used Retrofit2 over and over but I don't know how to solve this scenario.
I want to use an API which needs a query like query1, query2 etc. (Close to 100 values available)
For Example:
When I send a request via "query1" the response object has coordinates: List<List<List<Double>>>
When I send a request via "query2" the response object has coordinates: List<List<Double>>
When I send a request via "query3" the response object has coordinates: List<List<List<List<Double>>>>
When I send a request via "query4" the response object has coordinates: List<List<Double>>
I don't know how API returns the inner list count.
By the way there is just one coordinates in the object.
The response object in JSON format
[
{
"key-1": "value-1",
"key-2": "value-2",
"key-3": "value-3",
"key-4": {
"inner-key": [
[
[
1.0,
1.0
],
[
1.0,
1.0
]
]
]
}
},
{
"key-1": "value-1",
"key-2": "value-2",
"key-3": "value-3",
"key-4": {
"inner-key": [
[
[
[
1.0,
1.0
],
[
1.0,
1.0
]
],
[
[
1.0,
1.0
],
[
1.0,
1.0
]
]
]
]
}
}
]
Here is my data classes. But I don't know how can I define the "key4" object.
data class Response(
val key1: String? = null,
val key2: String? = null,
val key3: String? = null,
val key4: key4? = null,
)
data class key4(
//How should I define the "inner-key" object
)
Any suggestions would be helpful.

Solution:
I tried a few way for solution. I didn't know it's perfect solution but there is my solution:
There is my data class which name is key4 on my question.
data class key4(
val inner-key: List<Any>? = null
)
Also I cast inner-key like:
val temp = response?.key4?.inner-key as List<List<Double>>?
or
val temp = response?.key4?.inner-key as List<List<List<Double>>>?

Related

Need help parsing this type of json in Android using retrofit for API response handling

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.

Parse json map in Retrofit

I have web service that returns json response like this
"map": [
[
0,
"A mother of our mother"
],
[
2,
"A brother of our father"
],
[
1,
"A daughter of our sister"
]
],
How do i define data class to handle this response?
data class Map(
#SerializedName("map")
val map : <What type/class definition here>
)
This JSON represent array of objects array.
data class Map(
#SerializedName("map")
val map : List<List<Any?>>
)
That would be something like that:
data class Map(
#SerializedName("map")
val map : List<Collection<Any>>
)
I've been able to parse it with this test:
#Test
fun testJson() {
val myMap = Gson().fromJson(json, Map::class.java)
myMap.map.forEach { it ->
println(it)
}
assert(myMap != null)
}
Remember to wrap your json with {} for the test

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.

Multiple Data Types in JSON

I am accessing a API that has geometries object types in an JSON Object. The issue I have is in my Kotlin model class (below), I cannot have two Object Types for the node, as I get hit with the error
java.lang.IllegalStateException: Expected a double but was BEGIN_ARRAY at line 12 column 16 path $.features[0].geometry.geometries[0].coordinates[0];
because for the each inner node coordinates ofeach object, it can either return:
List<Double>
List<List<Double>>
API JSON
"geometries": [
{
"type": "Point",
"coordinates": [153.0533903,-26.7735391]
},
{
"type": "LineString",
"coordinates": [
[153.0258962, -27.3399828],
[153.02596, -27.34007],
[153.02602, -27.34015],
[153.026028, -27.3401745]
]
}
]
...
...
...
The external API response is passed through Retrofit models, however in my model class how do provide both List<Double> & List<List<Double>> processing
public class EventsGeometriesAPINodeModel {
#SerializedName("type")
#Expose
internal var type: String? = null
//how do provide both List<Double> List<List<Double>> processing
//API retruns either:
//List<Double>
//List<List<Double>>
#SerializedName("coordinates")
#Expose
internal var coordinates: List<Double>? = null
}
You have to make coordinates variable dynamic like JsonArray and whenever you receive data then you have to check type of that variable.
val listCoordinates = jsonObject.coordinates
if(listCoordinates is List<Double>){
} else if(listCoordinates is List<List<Double>>){
}

GSON mapping without entity name

I need to map a JSON object to a class using GSON, here is the JSON object:
{
"protocols": [
[ "https", 39 ],
[ "http", 1 ]
],
...
}
Generally if there are entity names specified it is easy to do something like this:
{
"protocols": [
[ "name":"https", "count":39 ],
[ "name":"http", "count":1 ]
],
...
}
class ProtocolItem {
#SerializedName("name")
String protocolName;
#SerializedName("count")
int count;
}
However since no entity names are specified in this case, I am not sure how to do the mapping for this. Please point some directions for me if you are familiar with the case.
Thanks
Unlike your first example,
[ "name":"https", "count":39 ],
is invalid JSON as you can either specify an array using [1, 2] without any names or a map using {"x": 1, "y": 2} with "entity names" as keys. So the solution is simple:
{
"protocols": [
[ "https", 39 ],
[ "http", 1 ]
],
}
is a map with a single key and a value which is an array of arrays of objects. You can map it as
class All {
Object[][] protocols;
}
You must use Object here, as it needs to accept both strings and ints. Instead of arrays, you can use Lists.
I guess, you'd prefer to serialize it as
class All {
Map<String, Integer> protocols;
}
This is possible, too, but you need a TypeAdapter. The very first linked example shows clearly how to do it (start with beginArray, in a loop test JsonToken.BEGIN_ARRAY and do nextString and nextInt, etc.).

Categories

Resources