How to generate dynamic json object for request body - android

In the Request body below, the number of value "questionOne", "questionTwo", etc changes for each student. How can i dynamically generate request body to fit the changing value of the key and value.
Sample request one
"quiz": {
"name":"Jacob",
"sid": "STD_500",
"questionOne":"",
"questionTwo":""
}
Sample request two
"quiz": {
"name":"Annie",
"sid": "STD_200",
"questionOne":"",
"questionTwo":""
"questionThree":"",
"questionFour":""
}
Data class:
data class Quiz (
val name : String?,
val sid : String?,
val questions: HashMap<String, String>?
)

I suppose the only way would be to define quiz as being a HashMap instead of a Quiz object.
I'm guessing you now have a RequestBody somewhere something like this?
data class RequestBody(
val quiz: Quiz
)
Then change it to
data class RequestBody(
val quiz: HashMap<String,String>
)
But it's kind of a bad design like this, I suggest to work out with the backend a solution as proposed by Tornike's answer

From your description, this is a bad design decision from backend side
You should have one parameter questions on which you will pass list of Question classes like this
First create a separate data class Question
data class Question (
val key:String,
val value:String)
than set list of this data class as Type of questions parameter in a request model like this
data class Quiz (
val name : String?,
val sid : String?,
val questions:List<Question>
)
I'm assuming you are using Gson library for converting data classes to json and vice versa
Solution for given situation is to create Separate request models for each number of questions you send to BE,
BUT i would strongly advise not to do this and make backend guys to change how your api works

The questions should be in a json array. Example:
"quiz": {
"name":"Jacob",
"sid": "STD_500",
"questions" : [
{"key": "questionOne", "value": ""},
{"key": "questionTwo", "value": ""},
]
}

Related

Correct way to parse eterogeneous arrays of data

I have this model that I would like to parse from JSON:
class CFInsertedValuesStructure {
#SerializedName("id")
val id : Int? = null
#SerializedName("value")
val value : List<String> = listOf();
#SerializedName("field_id")
val field_id : String? = null
}
There is a problem with the parameter "value" because it isn't always an array of String, sometimes it could be just a String type.
So when happens I would like to recognise it and create an array of just one String.
depending on what library you use the json parsing it may require a custom parsing type e.g. for kotlinx.serialization you might need to do something like a custom serializer
https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#specifying-serializer-on-a-property
better still : tell you server-side developer it should always be an array!

How to make base class accept generics which have different key

I am getting a response something like this where status, message & data keys will remain the same across the different API responses & only the content inside the data JSON object will change, in this example, I have a JSON object with a member key in other response I can have tournament key.
{
"status": true,
"message": "Success",
"data": {
"member": {
"id": 21,
"emailAddress": "abc#xyz.com",
"firstName": "ABC"
}
}
}
Currently, I am making use of generics and doing something like this
data class SignInResponse(
val `data`: Data<Member>
) : BaseResponse()
and BaseResponse class has common JSON keys that I am getting, Here I'm using generics in Data class passing the JSON class that is changing.
open class BaseResponse {
val status: Boolean = false
val message: String = UNDEFINED
}
#Keep
data class Data<T>(val actualData: T)
But this approach is incomplete because the above code will expect a JSON key actualData but here JSON key can be a member , tournament , or anything. How can I pass the class in Data class so that it can support the above JSON response?
Is this as a result from a network call? I've never used generics in trying to parse incoming network calls before.
I'd have a baseData class that contains any common fields between member or tournament - like id or name and then subclass that with a concrete implementation for each possibility which would hold the unique data.
then your BaseResponse class could just be
data class BaseResponse(
val status: Boolean
val message: String
val data: BaseData
)
One way to represent this would be via a class hierarchy rather than generics, because this extra wrapping is not present in the JSON, nor in your conceptual data structure.
A sealed class is what I would usually go for in this case.
However, it all depends on which JSON serialization library you're using. If you control both ends of the communication, I would definitely go for Kotlinx Serialization, which supports sealed hierarchies (and polymorphism in general) in a nice way.
If you don't control the serialization side, maybe a temporary union of all fields as #Chris mentioned is your simplest option.
I ended up using the following approach, where I create a generic for data class
data class BaseResponse<T>(
val status: Boolean, val message: String, val data: T
)
Above implementation enables me to pass model of changing JSON Object inside the data field
data class SignInResponse(
val token: String, val member: Member
)
and after summing up everything we can use it like this
interface BasicxBookingServices {
#POST(Urls.SIGNIN_URL)
suspend fun signIn(#Body body: SignInRequest): Response<BaseResponse<SignInResponse>>
}

Extract data from json to a kotlin data class

I have to use a json file in my app and I have defined a different data classes to represent the data itself. the json is stored locally in the app.
the json look like:
[
{
"id":987847,
"type":"FixtureUpcoming",
"homeTeam":{
"id":43,
"name":"Manchester City",
"shortName":"Man City",
"abbr":"MNC",
"alias":"t43"
},
"awayTeam":{
"id":8,
"name":"Chelsea",
"shortName":"Chelsea",
"abbr":"CHL",
"alias":"t8"
},
"date":"2019-02-10T16:00:00.000Z",
"competitionStage":{
"competition":{
"id":8,
"name":"Premier League"
}
},
"venue":{
"id":2691,
"name":"Etihad Stadium"
},
"state":"preMatch"
},
{
"id":1036495,
"type":"FixtureUpcoming",
"homeTeam":{
...
]
So I have created a data class which represent an element of this array as below:
data class FixtureItem(
var id: Int,
var type: String,
var homeTeam: Team,
var awayTeam: Team,
var date: String,
var competitionStage: List<Competition>,
var venue: Venue,
var state: String
)
and some other elements are also a defined class like Team or Venue..
data class Team(
var id: Int,
var name: String,
var shortName: String,
var abbr: String,
var alias: String
)
What is the best way to translate this json using data class ? overall, this json is display in a recyclingview.
Any idea how to properly extract it and use it in a recyclerview ?
Thanks
Personally I would use Moshi or KotlinX Serialization to parse your data into objects
Moshi adds a few extra dependencies required for kotlin use and KotlinX Serialization was made by the kotlin team in 100% kotlin plus supports kotlin multiplatform if that's of any interest. It is fairly new however and just went 1.0 a few weeks ago.
cant really go wrong with either though

Kotlin Deserialization - JSON Array to multiple different objects

I'm using the 1.0.0 version of kotlin serialization but I'm stuck when I try to deserialize a "flexible" array.
From the Backend API that I don't control I get back an JSON Array that holds different types of objects. How would you deserialize them using kotlin serialization?
Example
This is the API's response
[
{
"id": "test",
"person": "person",
"lastTime": "lastTime",
"expert": "pro"
},
{
"id": "test",
"person": "person",
"period": "period",
"value": 1
}
]
#Serializable
sealed class Base {
#SerialName("id")
abstract val id: String
#SerialName("person")
abstract val person: String
}
#Serializable
data class ObjectA (
#SerialName("id") override val id: String,
#SerialName("title") override val title: String,
#SerialName("lastTime") val lastTime: String,
#SerialName("expert") val expert: String
) : Base()
#Serializable
data class ObjectB (
#SerialName("id") override val id: String,
#SerialName("title") override val title: String,
#SerialName("period") val period: String,
#SerialName("value") val value: Int
) : Base()
Performing the following code result in an error
println(Json.decodeFromString<List<Base>>(json))
error Polymorphic serializer was not found for class discriminator
When you say you don't control the API, is that JSON being generated from your code by the Kotlin serialization library? Or is it something else you want to wrangle into your own types?
By default sealed classes are handled by adding a type field to the JSON, which you have in your objects, but it's a property in your Base class. In the next example it shows you how you can add a #SerialName("owned") annotation to say what type value each class corresponds to, which might help you if you can add the right one to your classes? Although in your JSON example both objects have "type" as their type...
If you can't nudge the API response into the right places, you might have to write a custom serializer (it's the deserialize part you care about) to parse things and identify what each object looks like, and construct the appropriate one.
(I don't know a huge amount about the library or anything, just trying to give you some stuff to look at, see if it helps!)
#cactustictacs solution came very close. He said that "By default sealed classes are handled by adding a type field to the JSON"
But because I didn't had a type property I needed a other field that decides which subclass it should be.
In Kotlin Serializer you can do that by
val format = Json {
classDiscriminator = "PROPERTY_THAT_DEFINES_THE_SUBCLASS"
}
val contentType = MediaType.get("application/json")
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.client(client)
.addConverterFactory(format.asConverterFactory(contentType))
.build()
where in classDiscriminator you can enter the property that you want. Hope this helps other people in the future.

Json deserializing with file that contains more than 300 classes

i am creating an app that reads some info from api about many countries, using Retrofit2. I can use the plugin to automatically create classes based on json file but it generates something like 350+ data classes. Can I do it in any other way? Or that number of classes is necessary?
Json contains "countries" node that has got 350+ country children like "Afganistan", "Boliwia" etc
you must change the structure of your API response that sends information of each country as a JSONArray object instead of JSONObject with the key set to the country name.
like this :
[
{
name : "Afganistan",
people : 12334,
...
},
...,
]
If all the countries are compatible with the same data-representation, then you can just create a class which holds all the information in it:
class Country(val countryName: String, val prop: String, val otherprop: OtherType, ...)
fun JSONObject.toCountryWith(name: String) =
Country(name, getString["prop"], ...)
You may create a JSONObject of the json first, and then iteratre over it creating the list of Country:
val list = mutableListOf<Country>()
JSONObject(yourJsonString).apply {
for(key in keys()) {
list.add(getJsonObject(key).toCountryWith(key))
}
}
// list is ready with objects of Country

Categories

Resources