Deal with dynamic response in retrofit - android

I'm receiving json like this
{
"animal" : {
"type" : "FIRE",
"food" : "potato",
"water": "7up"
}
}
---
{
"animal" : {
"type" : "WATER",
"water": "7up"
}
}
---
{
"animal" : {
"type" : "CYPO",
"counter": 7
}
}
---
{
"animal" : {
"type" : "UNKNOWN",
"food": "Stup",
"water": "Cola",
"counter" : 4
}
}
Am I suposed to create 4 data classes? I mean, I know how they are going to look, they are not going to change, but depends of the type is going to have some parameters or not, so do I have to create 4 data class like for instance the FIRE one
data class AnimalFireResponse{
#SerializedName("type") val myEnum: MyEnum, #SerializedName("food") val food : String, #SerializedName("water") val water : String)
}
But then I do not know what to put on my service
#GET("/dynamic/stuff.php")
fun getAnimal(): Call<List<MyResponseWithDynamicAnimal>>
Is there any other way way generic?
Edit
Maybe it helps but, I know all the type values (FIRE,WAITER,CYPO,UNKNOWN) and I know all of them are going to return the same values, for instance FIRE is going to return always food and water, does it help to you guys?

Yes, you can make a single data class to represent this JSON.
As the names of the fields don't change you can include them all and mark them as nullable.

I suppose you could create 1 data class with all the possible fields and use the fields accordingly. What i mean is:
data class AnimalResponse{
#SerializedName("type") val myEnum: MyEnum,
#SerializedName("food") val food : String= "",
#SerializedName("water") val water : String= "",
#SerializedName("counter") val counter : Integer = -1)
}
If your response doesn't contain a field, it will be initialized with a default value which you can check in your code. However, I feel like the API structure may not be well optimized.(I could be wrong, I am no expert on this)

Use the following DTO if nullable is ok
data class AnimalResponse(
val type: String?,
val food : String?,
val water : String?,
val counter : Integer?
)

Related

Unexpected Error : Unexpected character encountered while parsing value: }. Path 'resultData.data', line 1, position 184

I don't understand what is problem clearly. When I searched it in google, I don't decide my reponse model is problem or the json response is problem and should change. Which one? I can't find solution for Kotlin. How I should solve this?
response JSON:
"data":{
"productInfo":{
"data":{
"toBarcode":"2704439285463",
"productJson":{
"p_no":"28420000",
"p_name":"ASA"
}
}
},
"moves":{
"data":[
{
"fisAcik":"MALVERENDEN",
"toBarcode":"2704439285463",
"toJson":{
"to_Hks_Adi":"DAĞITIM MERKEZİ"
},
"movementJson":{
"isleme_Tarihi":"21/12/2022 02:19:30"
}
}
]
}
}
Data.kt
data class Data(
val productInfo: ProductInfo,
val moves: Moves
)
data class Moves (
val data: List<MovesItem>
)
data class MovesItem (
#SerializedName("fisAcik")
val receiptExplanation: String,
val toBarcode: String,
val toJson: ToJson,
val movementJson: MovementJson
)
data class MovementJson (
#SerializedName("isleme_Tarihi")
val processDate: String
)
data class ToJson (
#SerializedName("to_Hks_Adi")
val toUnitHksName: String
)
data class ProductInfo (
val data: ProductInfoItems
)
data class ProductInfoItems (
val toBarcode: String,
val productJson: ProductJson
)
data class ProductJson (
#SerializedName("p_No")
val migrosProductNo: String,
#SerializedName("p_Name")
val migrosProductName: String
)
method that using to call request.
suspend fun dataGetInfo(#Body request: DataRequest): NetworkResult<BaseResponse<Data>>
The framework you are using for this:
...fun dataGetInfo(#Body request: DataRequest)...
is implicitly taking a JSON request and deserializing.
The annotation #SerializedName is a from the Gson library, so I guessed that your framework must be using Gson. From that I was able to test using:
import com.google.gson.Gson
import com.google.gson.annotations.SerializedName
println(Gson().fromJson(src, Data::class.java))
which produces
Data(productInfo=ProductInfo(data=ProductInfoItems(toBarcode=2704439285463, productJson=ProductJson(migrosProductNo=null, migrosProductName=null))), moves=Moves(data=[MovesItem(receiptExplanation=MALVERENDEN, toBarcode=2704439285463, toJson=ToJson(toUnitHksName=DAĞITIM MERKEZİ), movementJson=MovementJson(processDate=21/12/2022 02:19:30))]))
So fundamentally your code is ok, but I think the problem is how the source JSON is "topped and tailed". To get that parse work, I was using
val src = """
{
"productInfo": {
"data": {
"toBarcode": "2704439285463",
"productJson": {
"p_no": "28420000",
"p_name": "ASA"
}
}
},
"moves": {
"data": [
{
"fisAcik": "MALVERENDEN",
"toBarcode": "2704439285463",
"toJson": {
"to_Hks_Adi": "DAĞITIM MERKEZİ"
},
"movementJson": {
"isleme_Tarihi": "21/12/2022 02:19:30"
}
}
]
}
}
"""
Notice how I removed, from your source, "data": since what you pasted is obviously not a JSON document. I guess, therefore, that this is where the problem occurs - something to do with the top or bottom of the JSON document or you need a container object around the JSON for Data
This error was from my wrong request. I saw Ios has same error also when request with wrong value. So, for who will look this quesiton, they should understand it's not from response or kotlin. Check your value it is clearly what request need.

How to generate dynamic json object for request body

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": ""},
]
}

Can't convert String to data class objects - Firebase realtime database [duplicate]

This question already has an answer here:
com.google.firebase.database.DatabaseException: Can't convert object of type java.lang.String to type Data class object KOTLIN
(1 answer)
Closed 1 year ago.
I have read other comments on this same issue, but none of them has touched on a situation like mine
In mine, below describes how the data is structured:
{
"symbols":{
"alphabets":{
"a":{
"available":true,
"text":"A",
"timestamp":1.512686825309134E9
},
"b":{
"available":true,
"text":"B",
"timestamp":1.512687248764272E9
}"NameOfSymbols":"alphabets"
}
}
}
*The reason why mine is showing the error is that it can't convert the string "NameOfSymbols" : "alphabets" to the objects as specified in the data class
So, what can be done about it, I use Kotlin
Is there a way I can exclude that part of the children value while I only get the one that is specified in the data class?
Data Class
data class alphabets(
val name: Names,
var NameOfSymbols: String? = null) {
data class Names(
var available: Boolean? = null,
var text: String? = null,
var timestamp: Long? = null) {
}
}
This structure might work for your case (untested):
data class Message(
#PropertyName("symbols") val symbols: Symbols,
)
data class Symbols(
#PropertyName("alphabets") val alphabets: Alphabets,
)
data class Alphabets(
#PropertyName("a") val a: Alphabet,
#PropertyName("b") val b: Alphabet,
#PropertyName("NameOfSymbols") val nameOfSymbols: String,
)
data class Alphabet(
#PropertyName("available") val available: Boolean,
#PropertyName("text") val text: String,
#PropertyName("timestamp") val timestamp: Long,
)
Usage would be:
// in your ValueEventListener
override fun onDataChange(snapshot: DataSnapshot) {
val value = snapshot.getValue<Message>()
}
If you want to exclude your NameOfSymbols, you should remove it, and add the #IgnoreExtraProperties, like shown below:
#IgnoreExtraProperties
data class Alphabets(
#PropertyName("a") val a: Alphabet,
#PropertyName("b") val b: Alphabet,
)
NOTE, I used these versions of firebase database:
implementation 'com.google.firebase:firebase-database:19.7.0'
implementation 'com.google.firebase:firebase-database-ktx:19.7.0'
ok, After reading the documentation on Structuring Database on the firebase docs website, Structure your database
I realised that i didn't structure my database well, i should have regrouped them after specifying the name
like below
{
// This is a poorly nested data architecture, because iterating the children
// of the "chats" node to get a list of conversation titles requires
// potentially downloading hundreds of megabytes of messages
"chats": {
"one": {
"title": "Historical Tech Pioneers",
"messages": {
"m1": { "sender": "ghopper", "message": "Relay malfunction found. Cause: moth." },
"m2": { ... },
// a very long list of messages
}
},
"two": { ... }
}
}
Although, it said that structuring it that way isn't a nice way, it seems that's what works for me

endAt() with limitToLast() in Firebase query is not working as expected in android

I am using the firebase real-time database to store chat messages schema is like below
"chat_message" : {
"up-29709425-fef2-490c-8335-77dbeafbce1e" : {
"-MLhPH_i7bfvnTpccK1k" : {
"chat_room_id" : "up-29709425-fef2-490c-8335-77dbeafbce1e",
"created_at" : "2020-11-09T19:59:11+05:30",
"creator_id" : 15456623,
"message" : "hii",
"message_type" : "text",
"time_in_ms" : 1604932151865
}
},
"up-299bcd2f-90bb-4ca2-b526-7e858d2d98fe" : {
"-MLhWydTKmB52W_XFtS6" : {
"chat_room_id" : "up-299bcd2f-90bb-4ca2-b526-7e858d2d98fe",
"created_at" : "2020-11-09T20:32:47+05:30",
"creator_id" : 15451746,
"message" : "hoi",
"message_type" : "text",
"time_in_ms" : 1604934167159
}
}
The problem is for the first time, I am getting 10 items and after that, I am not able to get the next 10 items. Here are the queries I am using
private var query: Query? = null
private fun getInitialChatMessages(time: String?, key: String?) {
query = if (time == null) {
// For first time I am getting last 10 items
getFirebaseDbReference()
.child(FIREBASE_DATABASE_LOCATION_MESSAGES)
.child(project?.userProjectId!!)
.orderByChild("time_in_ms")
.limitToLast(INITIAL_FETCH_MESSAGE_COUNT)
} else {
// on scroll its not working
getFirebaseDbReference()
.child(FIREBASE_DATABASE_LOCATION_MESSAGES)
.child(project?.userProjectId!!)
.orderByChild("time_in_ms")
.endAt("$time")
.limitToLast(INITIAL_FETCH_MESSAGE_COUNT)
}
query?.addListenerForSingleValueEvent(listener)
}
Any help would be appreciated. Thanks in advance.
The time property in your database is a numeric value, but in your code you're passing it as a string. When the database compares a number and a string, they are never the same.
So you'll want to fix your code so that is also treats the time as a number, or at the very least pass is as a number to the database by calling toDouble() on it.

How to create kotlin data class for this network service?

I want to know what approach you will take to make data class in this situation, In this JSON service
I have subCategories under the other-services field. every item in the subCategories has a slug field which is also one of the field in other data elements like - digital-currency, picture, print. now my requirement is I want to pick up appropriate data class based on user selection of subCategories item. if the user has selected below item
{
"title": "電子マネー",
"slug": "digital-currency"
}
then I should be able to match the slug field and then should pick up the digital-currency data element
"digital-currency": {
"slug": "digital-currency",
"title": "電子マネー",
"_id": "7j6OzPKVzim7xvW8BvI8zV",
"isSub": true,
"parent": "other-services"
}
how can I make data class for this ?
You could simplify your code by..
data class NetworkResponse(
val otherServices: OtherServices,
val digitalCurrency: SubCategory,
val picture: SubCategory,
val print: SubCategory
) {
fun getUserSelectedCategory(slug : String) : SubCategory? {
return when (slug) {
"digital-currency" -> digitalCurrency
"picture" -> picture
"print" -> print
else -> null
}
}
}
data class OtherServices(val subCategory: List<SubCategory>)
data class SubCategory(val title: String, val slug: String)
Below is my solution , If I understand it properly , it is nothing but finding object of given type in a heterogeneous List. here DigitalCurrency, Picture, Print, pet-supplies,... so on , are all heterogeneous type. To pick any one of this item , you uses slug field as identifier, you must have mapping mechanism to pick right data object for given type with given json structure.
however solution of mine goes adding WHEN condition every time when new data element added at backend. I believe , as per the requirement and Json structure made here , this cannot be generalize to any extend where It can map to appropriate data class without any additional changes in Code in future.
if you think that Data class can be construct in such way that it doesn‘t need any change in code to accommodate newly added data , Please let me know.
data class NetworkResponse(
val otherServices: OtherServices,
val digitalCurrency: DigitalCurrency,
val picture: Picture,
val print: Print
) {
fun getUserSelectedCategory(slug : String) : BaseClass {
when (slug) {
"digital-currency" -> {
return digitalCurrency
}
"picture" -> {
return picture
}
"print" -> {
return print
}
}
}
}
data class OtherServices(val subCategory: List<SubCategory>) {
}
interface BaseClass
data class SubCategory(val title: String, val slug: String) : BaseClass
data class DigitalCurrency(val title: String, val slug: String) : BaseClass
data class Picture(val title: String, val slug: String) : BaseClass
data class Print(val title: String, val slug: String) : BaseClass

Categories

Resources