How to convert json object with objects to json array with objects - kotlin

I have a json object that looks like this.
{
"Items": {
"zzzz": {
"id": "zzzz",
"title": "qqqqqqq",
"notifications": []
},
"rrrrr": {
"id": "rrrrr",
"title": "rrrrrrrrrrrrrrrrrr",
"notifications": []
},
"eeeee": {
"id": "eeeee",
"title": "eeeeeeeeeeeeeeeeeeee",
"notifications": []
},
"wwww": null,
"dddddd": {
"id": "dddddd",
"title": "ddddddddddddddddddddddddd",
"notifications": []
},
"qqq": {
"id": "qqq",
"title": "qqqqqqqqqqqqqqqqqqqqqq",
"notifications": []
},
"rrrrrr": null
}
}
My data class:
data class Response(
val Items: List<Notification>
........)
data ckass Notification(
val id : String,
val title: String,
val notifications: List<...>,
I need a List with objects zzzz,rrrr and so on to get into the data class with val items. But I can't figure out how to convert the incoming json object to a json array
I wanted to use my own deserializer, but in my case it won't help because I use one instance of okhttp and retrofit for all requests. And also, a response always comes from the server in the form of:
"Items": {
//other request body
},
.....
}

I am not sure what deserializer you using. Here's a solution assuming Jackson, but maybe you can take the ideas from this if you are using Gson, etc.
The key idea is to use an intermediary object to deserialize into - a Map whose key values you ignore:
// your desired data classes
data class Response(
val items: List<Notification>,
)
data class Notification(
val id: String,
val title: String,
val notifications: List<Any>,
)
// an intermediary object
// I notice that some Notifications are null, hence the `?`
data class ResponseWithObjects(
#JsonProperty("Items") // this is needed for Jackson since I used a conventional variable name Kotlin side
val items: Map<String, Notification?>,
)
fun main(args: Array<String>) {
val actualResponse: ResponseWithObjects = TestUtils.deserialize("/test.json", ResponseWithObjects::class)
println(actualResponse)
val desiredResponse = Response(
items = actualResponse.items
.values.filterNotNull() // assuming you don't want the null notifications in the resultant array
.toList(),
)
println(desiredResponse)
}

To convert the given JSON object to a list of Notification objects, you can iterate over the key-value pairs in the "Items" object and create a Notification object for each non-null value. Here's some sample Kotlin code that demonstrates this:
val json = // the JSON object from your example
val itemsObject = json.getJSONObject("Items")
val notifications = mutableListOf<Notification>()
for (key in itemsObject.keys()) {
val item = itemsObject.getJSONObject(key)
if (item != null) {
val notification = Notification(
item.getString("id"),
item.getString("title"),
// add logic to parse notifications list here
)
notifications.add(notification)
}
}
val response = Response(notifications)
Note that you'll need to fill in the logic to parse the "notifications" list for each Notification object. If it's just an array of strings, you can use item.getJSONArray("notifications").toList() to get a list of strings.

Related

How do I GET the field values from a firestore document using retrofit in jetpack compose

I am trying to read some images from firestore collection using retrofit into my app
This is how my JSON looks like
"documents": [
{
"name": "projects/la370210/databases/(default)/documents/Photos/ahCinu5bJVwe",
"fields": {
"img_url": {
"stringValue": "https://firebasestorage.googleapis.com/v0/b/pq-villa-370210.appspot.com/o/PQPhotos%2Fsitting_area_anex.jpg?alt=media&token=0d02643e-b69d-4031-b51f-"
},
"img_des": {
"stringValue": "Annex hall"
}
},
"createTime": "2022-12-01T05:14:58.593903Z",
"updateTime": "2022-12-01T05:14:58.593903Z"
},
...]
What I want is to get the value for the stringValue for 'img_url' and 'img_des' to my data class variables.
My data class
#Serializable
data class Photos (
#SerialName(value = "img_url")
val imgUrl: List<String>,
#SerialName(value = "img_des")
val imgDes: List<String>,
)
The Api interface
`
interface PQApiService {
#GET("PQPhotos")
suspend fun getPhotos(): List<PQPhotos>
}
`
In my DefaultAppContainer I have
`
private val BASE_URL =
"https://firestore.googleapis.com/v1/projects/$PQ_PROJECT_ID/databases/(default)/documents/"
private val retrofit = Retrofit.Builder()
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.baseUrl(BASE_URL)
.build()
`

How can I create a model for this API (Kotlin-Retrofit)?

I'm using https://www.api-football.com to get football countries and leagues, Json response for countries is like this:
{
"get": "countries",
"parameters": [],
"errors": [],
"results": 164,
"paging": {
"current": 1,
"total": 1
},
"response": [
{
"name": "Albania",
"code": "AL",
"flag": "https://media.api-sports.io/flags/al.svg"
},
{
"name": "Algeria",
"code": "DZ",
"flag": "https://media.api-sports.io/flags/dz.svg"
},
{
"name": "Andorra",
"code": "AD",
"flag": "https://media.api-sports.io/flags/ad.svg"
},
...
I tried to create a model like this:
Here is the CountryData data class:
data class CountryData (
val code: String,
val flag: String,
val name: String
)
And here is the Country data class which uses CountryData and use them in a list:
data class Country(
val results : List<CountryData>
)
But those models give me some errors: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was NUMBER at line 1 column 61 path $.results
And I think that's because of the api response type is not a list of CountryData.
So if that's the problem, I want to get only the part after "response": [ ... But I don't know how to do this.
If you think another problem causes this error please let me know.
By the way, here is the api interface:
interface FootballApi {
#Headers("X-RapidAPI-Key: $API_KEY")
#GET(GET_COUNTRIES)
suspend fun getCountries() : Response<Country>
}
And here is my Retrofit Instance :
object RetrofitInstance {
val api: FootballApi by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(FootballApi::class.java)
}
}
I solved this problem by adding other data models to my project.
Here is the Country data class:
data class Country(
val code: String,
val flag: String,
val name: String
)
Here is the CountryResponse data class:
data class CountryResponse (
val get: String,
val parameters: List<Any>,
val errors: List<Any>,
val results: Int,
val paging: Paging,
val response: List<Country>
)
Here is the Paging data class:
data class Paging(
val current: Int,
val total: Int
)
And now I can get the country list by response.body()!!.response
The problem you had in the first place is that parameter names in your model do matter! At first you tried to unmarshal the JSON data into the wrong model:
data class Country(
val results : List<CountryData>
)
The JSON field with all the countries in it has the name response like this:
"response": [
{
"name": "Albania",
"code": "AL",
"flag": "https://media.api-sports.io/flags/al.svg"
},
...
So if you use the same attribute in your model it should work without the need of adding all the other attributes (which you probably don't need) to your model:
data class Country(
val response: List<CountryData>
)

Looking for the simplest way to unwrap root element for certain responses with FeignClient

I have a FeignClient which communicates with an external service, looks like below:
interface JiraClient {
\\ ...
#RequestMapping(method = [RequestMethod.GET], value = ["\${jira.api.agile}/sprint/{sprintId}/issue"])
fun getIssuesForASprint(
#PathVariable("sprintId") sprintId: Int,
#RequestParam(name = "startAt", required = false) startAt: Int = 0,
#RequestParam(name = "maxResults", required = false) maxResults: Int = MAX_RESULTS
): IssueHolder
#RequestMapping(method = [RequestMethod.GET], value = ["\${jira.api.insight}/objectschema/list"])
fun getObjectSchemaList(): List<ObjectSchema>
}
And here is my POJO:
#JsonIgnoreProperties(ignoreUnknown = true)
data class ObjectSchema(
#JsonProperty("id") val id: Int,
#JsonProperty("name") val name: String?,
#JsonProperty("objectSchemaKey") val objectSchemaKey: String?,
#JsonProperty("status") val status: String?,
#JsonProperty("created") #JsonFormat(
shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
) val created: Date?,
#JsonProperty("updated") #JsonFormat(
shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
) val updated: Date?,
#JsonProperty("objectCount") val objectCount: Int?,
#JsonProperty("objectTypeCount") val objectTypeCount: Int?
)
It won't work because the response has an additional root element:
{
"objectschemas": [
{
"id": 1,
"name": "APPS CATALOG",
"objectSchemaKey": "AC",
"status": "Ok",
"created": "2020-01-01T11:11:11.000Z",
"updated": "2020-01-01T11:11:11.000Z",
"objectCount": 123,
"objectTypeCount": 10
},
{
"id": 2,
"name": "ORG CATALOG",
"objectSchemaKey": "OC",
"status": "Ok",
"created": "2020-01-01T11:11:11.000Z",
"updated": "2020-01-01T11:11:11.000Z",
"objectCount": 456,
"objectTypeCount": 20
}
]
}
For the other responses, I have created holder data classes, because they have pagination info, that makes sense:
{
"expand": "schema,names",
"startAt": 0,
"maxResults": 200,
"total": 1024,
"issues": [
...
]
}
Goal:
I'd like to get it work, with an easy way to add some annotations (e.g.: # JsonRootName ) to certain data classes (but not all), so that I can unwrap the root value objectschemas out of the box. Please note, that the data class used here is only for deserialization purpose, I will not need it for serialization. And I don't wanna add a bunch of logic into fun feignDecoder(): Decoder. For the Feign Client interface, there is no chance to add any logic like DeserializationFeature.UNWRAP_ROOT_VALUE.
Does anyone know the easiest way to achieve this? Thank you very much.
The complete code snippets can be found here.

Type mismatch during Gson deserialize json response with kotlin

I am trying to serialize the json response below, but I am unsure how to do it.
This is the Json my backend returns:
[
{
"title": "Dummy section, should not be seen",
"type": "dummy_test",
"metadata": []
},
{
"title": "Title1",
"type": "categories_products",
"metadata": [
{
"id": "1272"
}
]
},
{
"title": "Title2",
"type": "categories_products",
"metadata": [
{
"id": "996"
}
]
}
]
This is my ExploreItem class:
data class ExploreItem(
#SerializedName("metadata") val metadata: List<Metadata> = listOf(),
#SerializedName("title") val title: String = "",
#SerializedName("type") val type: String = ""
) {
enum class ExploreItemType(val value: String) {
#SerializedName("unknown")
UNKNOWN("unknown"),
#SerializedName("other_companies")
OTHER_COMPANIES("other_companies"),
#SerializedName("categories_products")
CATEGORIES_PRODUCTS("categories_products"),
#SerializedName("popular_categories")
POPULAR_CATEGORIES("popular_categories")
}
}
data class Metadata(
#SerializedName("id") val id: String = ""
)
And now I am trying to serialize it in the repository like this:
Serializer.defaultJsonParser.fromJson(response.body!!.string(),ExploreItem::class.java )
but it doesn't work because it's expecting a list of ExploreItem. How can I rewrite the serializer expression to parse it into a list?
From your error
Type mismatch. Required:List Found:ExploreItem!
Post errors is very important, Gson is telling you that it wants a List and not an object of ExploreItem.
In other words, you are telling to Gson with the call Serializer.defaultJsonParser.fromJson(response.body!!.string(),ExploreItem::class.java )
"Hey Gson, from the string I want an object ExploreItem", and Gson is telling you "Hey my friend, you string start with [ ] for sure it is a list of something and not a single object."
You need to pass in the Serializer.defaultJsonParser.fromJson(response.body!!.string(),List<ExploreItem>::class.java)
P.s: I'm not sure about the Kotlin syntax

combine two lists and map to new class object with rxjava

Consider the following input : Calling network call will retrieve client object
data class Client(
val name: String,
val phoneNumber: String,
val frequentContacts: List<String>,
val allContacts: List<String>
)
what i need to do is to map similar names in frequentContacts and allContacts list in new object and subscribe on the output.
assume the response from network call will return this Client Object
{
"name": "Jack",
"phoneNumber": "90284302424",
"frequentContacts": [
"John",
"Sam"
],
"allContacts": [
"John",
"Adam",
"Peter",
"Kim",
"Sam"
]
}
what i need receive in subscribe newly create object .
data class clientViewModel(val name: String,val isFrequent: Boolean)
so in onSuccess i should have instance from clientViewModel
Expected output :
("John", true")
("Adam", false")
("Peter", false")
("Kim", false")
("Sam", true")
here what i am up to
clientRepository.getClientById(clientId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doAfterTerminate { view.hideProgress() }
.flatMap{it.frequentContacts}
.subscribe{
onSuccess(item: ClientViewModel){}
onError(){}
onFinish(){}
}
but this not work because once i used flat map i lose the allContacts list
any help ?
I read about GroupBy operator but i am using Single...
Honestly I'd just do this:
clientRepository.getClientById(clientId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doFinally { view.hideProgress() }
.map { client ->
client.allContacts.map { contact ->
clientViewModel(name = contact, isFrequent = client.frequentContacts.contains(contact))
}
}
.subscribeBy(onSuccess = { list: List<clientViewModel> ->
...
}, onError = { err -> ... })