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
Related
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": ""},
]
}
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!
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>>
}
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.
I am using Retrofit and Gson to communicate with server and parse responses.
I am receiving list of geometry objects list from server. But the coordinates field of the object might differ. For example, I am receving the following json from server:
{
"geometry":{
"type":"Point",
"coordinates": [
76.95210456848145,
43.2790799527603
]
}
}
Sometimes this object returns me in this format:
{
"geometry":{
"type":"Polygon",
"coordinates":[
[
[76.9478130340576,43.286265501840916],
[76.9482421875,43.276267985142056],
[76.95098876953125,43.27101863123778]
]
]
}
}
As you can see, sometimes coordinates field is just a list (in the first example). Sometimes, this field is 3-level deep list (in the second example).
As a result of this, I cannot correctly parse the list since format of the elements in the list is not the same.
How can I correctly parse this?
Currently, I am using this data class:
data class Geometry(
#SerializedName("coordinates")
val coordinates: List<List<List<Double>>>,
#SerializedName("type")
val type: String)
If you know that there are 2 versions of the file, create one inner class for each file. At time of parsing, identify the type of "coordinates" if it's the 1st type proceed with the 1st, if the second proceed with the second
inner class GeometryV2(
#SerializedName("coordinates")
val coordinates: List<List<List<Double>>>,
#SerializedName("type")
val type: String)
}
inner class GeometryV1(
#SerializedName("coordinates")
val coordinates: List<Double>,
#SerializedName("type")
val type: String)
}