Retrofit Kotlin and getting an Array? - android

Anyone here can give me a small hand with Retrofit? I am trying to get an Array from PHP.
This is my Response in Kotlin:
class QuestionnairesResponse (val status: Boolean, val message:String, val questionnaires: List<Questionnaire>)
Questionnaire class:
data class Questionnaire(
val id: Int,
val title: String,
val questions: List<Question>
)
Question class:
data class Question (
val id: Int,
val text: String
)
My JSON array output:
{
"questionnaires":[
{
"id":"1",
"title":"Are you hungry?",
"questions":{
"id":"1",
"text":"How is your passion? "
}
},
{
"id":"2",
"title":"How are you feeling?",
"questions":{
"id":"1",
"text":"How is your passion? "
}
},
{
"id":"5",
"title":"Is testing working?",
"questions":{
"id":"4",
"text":"How is the testing?"
}
}
],
"status":true,
"message":"Succefully got questionnaires!!!"
}
However, it seems it doesn't get the 'questions' part and it results in a failure. The strange thing is that If I change it to 'question' instead of 'questions' it doesn't result in a failure, and it gets the questionnaire details as ID and title, but stil; the question array is empty.
What is going wrong here? I can't seem to find the problem.
UPDATE: My backend needs to be fixed as questions need to be an array.
I just can't seem to wrap my head around on how to fix this issue when getting the data from MySQL....
My PHP code:
$sql = "SELECT questionnaires.id QuestionnaireId, questionnaires.title QuestionnaireTitle, questions.id QuestionId, questions.text Question
FROM questionnaires INNER JOIN questionnaireshasquestions qa ON qa.idQuestionnaire = questionnaires.id
INNER JOIN questions ON questions.id = qa.idQuestion";
$conn=$dbh->prepare($sql);
$conn->execute();
$result = $conn->fetchAll(PDO::FETCH_GROUP|PDO::FETCH_ASSOC);
while ($row = $conn->fetchAll()) {
$endResult['questionnaires']= $row;
}
//echo json_encode($endResult);
$idd = null;
$test = [];
foreach ($endResult['questionnaires'] as $questionnaire) {
if ($idd != $questionnaire[0]) {
$idd = $questionnaire[0];
$test['questionnaires'][] = [
'id' => $questionnaire[0],
'title' => $questionnaire[1],
'questions' => [
'id' => $questionnaire[2],
'text' => $questionnaire[3]
]
];
}
}
One questionnaire can have multiple questions.

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.

JSON Serialization / Deserialization Kotlin Android Jetpack Compose Table

I'm in the process of creating a table section in Jetpack Compose that deserializes the data from a JSON.
At this point, I'm simply trying to display the data (the table formatting and clean up will come later, so you can ignore that part). Things have been going okay until I got to a part where the table row values are an array of arrays. I'm getting this error:
Error:
Expected class kotlinx.serialization.json.JsonObject as the serialized body of ...TableRow, but had class kotlinx.serialization.json.JsonArray
Here is the part of the test JSON that is being parsed for this table:
{
"title": "TRANSACTION HISTORY",
"type": "Table",
"names": [
"Date",
"Amount",
"Lender",
"Borrower"
],
"values": [
[
"07\/01\/2014",
"$315,000",
"Steve's Lending Co.",
"Glenn Danzig"
],
[
"10\/13\/2011",
"$236,000",
"Feet Company",
"Marcus Toeswater"
]
]
},
{
"title": "ASSESSMENT & TAX",
"type": "Table",
"names": [
"Year",
"Property Taxes",
"Tax Assessment"
],
"values": [
[
"2017",
"$6,068",
"$395,000"
],
[
"2016",
"$5,864",
"$372,000"
],
[
"2015",
"$5,609",
"$341,500"
]
]
},
Here's the code I have right now. I'm simply trying to post two rows of data:
1 with the column header names, and
1 with all of the row data (doesn't have to look nice at this point - I'm just trying to get the mapping done)
#Serializable
#SerialName("Table")
#Parcelize
data class TableSection(
override val title: String? = null,
#SerialName("names")
val columnHeader: ArrayList<String?> = arrayListOf(),
#SerialName("values")
val tableData: ArrayList<TableRow> = arrayListOf(),
) : Section() {
#Composable
override fun Content(modifier: Modifier) {
return Column {
Row {
columnHeader.map {
Text(it ?: "")
}
}
Row {
tableData.map { row ->
row.tableRowValues.map { value ->
Text(value ?: "")
}
}
}
}
}
}
#Serializable
#Parcelize
data class TableRow(
#Serializable
val tableRowValues: ArrayList<String?> = arrayListOf(),
) : Parcelable
Note: The Title and the Table Column Headers work just fine. It's the table data that is throwing the error.
I ended up creating a custom serializer to get the nested values:
#Serializable(with = TableRowSerializer::class)
#Parcelize
data class TableRow(
val tableRowValues: List<String?> = arrayListOf(),
) : Parcelable
object TableRowSerializer : KSerializer<TableRow> {
private val serializer = ListSerializer(String.serializer())
override val descriptor: SerialDescriptor = serializer.descriptor
override fun serialize(encoder: Encoder, value: TableRow) {
val rowValues = value.tableRowValues
rowValues.let{rowValues as? List<String>}
?.let { encoder.encodeSerializableValue(serializer, it) }
}
override fun deserialize(decoder: Decoder): TableRow {
return TableRow(decoder.decodeSerializableValue(serializer))
}
}

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.

Return Array or object in Kotlin Dataclass

I'm pretty new to Kotlin so here I go.
I have some JSON data that sometimes returns an object or it can return an array. See example:
Object:
"planograms": {
"planogram": {
"multiple-facings": 1,
"planogram-seq": 838,
"planogram-id": 252871,
"planogram-desc": "Bulk Hardware - No Springs"
}
},
Array:
"planograms": {
"planogram": [
{
"multiple-facings": 1,
"planogram-seq": 112,
"planogram-id": 262027,
"planogram-desc": "Cat Food - Cat Dry"
},
{
"multiple-facings": 1,
"planogram-seq": 94,
"planogram-id": 276508,
"planogram-desc": "Cat Food - Cat Dry - A"
}
]
},
I am using Retrofit2 for the parsing so here is the code that does that:
fun getItemInfoExtended(#Body itemId: ItemData): Call<DefaultResponse>
DefaultResponse has more data that includes the object/array.
My question is how can I get to return the object or array. currently, I have a list in my data class, so when the object is returned I get an error stating the issue of not an array:
data class Planograms (
val planogram: List<Planogram>?
)
data class Planogram (
val multipleFacings: Long,
val planogramSeq: Long,
val planogramID: Long,
val planogramDesc: String
)
Thanks in advance for your input.

How to parse nested array insided a json object

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);
}

Categories

Resources