get array from inside many objects in kotlin - kotlin

l am try to build simple app provide flight schedule . the problem is l have many object in json url and the array list inside of these object and l cant to get array list from objects because l got error fatal Caused by: org.json.JSONException: Value
my data json api
{
"result": {
"response": {
"airport": {
"pluginData": {
"schedule": {
"arrivals": {
"data": [
{
"flight": {
"identification": {
"id": null,
"row": 4832637003,
"number": {
"default": "ZP4801",
"alternative": null
},
"callsign": null,
"codeshare": null
}
}
}
]
}
}
}
}
}
}
}
my code for getting data json of array list
private fun handleJson (jsonString: String?){
val jsonArray = JSONArray(jsonString)
val list = ArrayList<FlightShdu>()
var x = 0
while (x < jsonArray.length()){
val jsonObject = jsonArray.getJSONObject(x)
list.add(FlightShdu(
jsonObject.getInt("id"),
jsonObject.getString("callsign")
))
x++
}
val adapter = ListAdapte(this#MainActivity,list)
flightShdu_list.adapter = adapter
}

I would normally suggest for a full structure of the JSON via data classes, as this methodology could potentially be expensive to run multiple times over and over... The following highlights a method to dig into the JSON through jsonObjects by name, and then take the final layer of "identification" and populates a data class that is serializable with the resulting object
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.json.JsonDecodingException
import kotlinx.serialization.json.JsonElement
val data =
"""
{
"result": {
"response": {
"airport": {
"pluginData": {
"schedule": {
"arrivals": {
"data": [{
"flight": {
"identification": {
"id": null,
"row": 4832637003,
"number": {
"default": "ZP4801",
"alternative": null
},
"callsign": null,
"codeshare": null
}
}
}]
}
}
}
}
}
}
}
"""
#Serializable
data class FlightIdentification(
val id: Int?,
val row: String,
val number: IdentificationNumber,
val callsign: String?,
val codeshare: String?
) {
#Serializable
data class IdentificationNumber(
val default: String,
val alternative: String?
)
}
val json = Json(JsonConfiguration.Stable)
fun JsonElement?.get(name: String): JsonElement? {
return if (this == null) null
else this.jsonObject[name]
}
fun handleJson(jsonString: String) {
val obj = json.parseJson(jsonString)
val data = obj.get("result").get("response").get("airport").get("pluginData")
.get("schedule").get("arrivals").get("data")
if (data != null) {
val flight = data.jsonArray[0]
.get("flight").get("identification")
try {
val res = json.parse(FlightIdentification.serializer(), flight.toString())
println(res)
} catch (e: JsonDecodingException) {
println("Decode: ${e.message}")
}
}
}
handleJson(data)

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()
`

Polymorphic kotlinx serialization when type is integer, not string

I am trying to consume and emit JSON which contains a polymorphic list of items. The problem is: the items contain type key with integer values (not strings). The API endpoint produces and expects JSON similar to this:
{
"startTime": "2022-07-27T13:32:57.379Z",
"items": [
{
"type": 0,
"results": "string",
"restBetweenRounds": "string"
},
{
"type": 1,
"results": "string",
"setCount": 0
},
{
"type": 2,
"items": [
{
"type": 0,
"results": "string",
"restBetweenRounds": "string"
},
{
"type": 1,
"results": "string",
"setCount": 0
}
],
"results": "string"
}
],
"status": 0,
"clientId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
As described in the article on polymorphism, I created an hierarchy of classes. I also try to convert type value before deserialization.
object MyTransformingDeserializer : JsonTransformingSerializer<BaseItem>(PolymorphicSerializer(BaseItem::class)) {
override fun transformDeserialize(element: JsonElement): JsonElement {
val type = element.jsonObject["type"]!!
val newType = JsonPrimitive(value = type.toString())
return JsonObject(element.jsonObject.toMutableMap().also { it["type"] = newType })
}
}
#Serializable(with = MyTransformingDeserializer::class)
sealed class BaseItem {
abstract val type: String
}
#Serializable
#SerialName("0")
class ItemType0(
override val type: String,
// ...
) : BaseItem()
#Serializable
#SerialName("1")
class ItemType1(
override val type: String,
// ...
) : BaseItem()
#Serializable
#SerialName("2")
class ItemType2(
override val type: String,
// ...
) : BaseItem()
But all I get is this error:
kotlinx.serialization.json.internal.JsonDecodingException: Polymorphic
serializer was not found for class discriminator '0'
Given that I can not change the format of the JSON, what can be done to successfully serialize/desereialize it?
Handling polymorphism in Kotlinx Serialization is difficult, especially when you don't have control over the format of the source. But KxS does give a lot of low-level tools to manually handle almost anything.
You were close in choosing JsonTransformingSerializer! It seems that it doesn't transform the JSON before KxS selects a serializer. Because discriminators can only be strings, and so deserialization fails.
JsonContentPolymorphicSerializer
Instead of JsonTransformingSerializer, you can use JsonContentPolymorphicSerializer.
Kotlinx Serialization will first deserialize the JSON to a JsonObject. It will then provide that object to the serializer for BaseItem, and you can parse and select the correct subclass.
import kotlinx.serialization.*
import kotlinx.serialization.json.*
object BaseItemSerializer : JsonContentPolymorphicSerializer<BaseItem>(BaseItem::class) {
override fun selectDeserializer(
element: JsonElement
): DeserializationStrategy<out BaseItem> {
return when (val type = element.jsonObject["type"]?.jsonPrimitive?.intOrNull) {
0 -> ItemType0.serializer()
1 -> ItemType1.serializer()
2 -> ItemType2.serializer()
else -> error("unknown Item type $type")
}
}
}
Including type
Since this is manually performing polymorphic discrimination, there's no need to include type in your classes.
import kotlinx.serialization.Serializable
#Serializable(with = BaseItemSerializer::class)
sealed class BaseItem
#Serializable
data class ItemType0(
// ...
) : BaseItem()
#Serializable
class ItemType1(
// ...
) : BaseItem()
#Serializable
class ItemType2(
// ...
) : BaseItem()
However you might like to include it, for completeness, and so it's included when serializing. For that, you must use #EncodeDefault
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.Serializable
#Serializable(with = BaseItemSerializer::class)
sealed class BaseItem {
abstract val type: Int
}
#Serializable
class ItemType0(
// ...
) : BaseItem() {
#EncodeDefault
override val type: Int = 0
}
// ...
Complete example
Bringing it all together, here's a complete example.
import kotlinx.serialization.*
import kotlinx.serialization.json.*
val mapper = Json {
prettyPrint = true
prettyPrintIndent = " "
}
fun main() {
val json = """
{
"startTime": "2022-07-27T13:32:57.379Z",
"status": 0,
"clientId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"items": [
{
"type": 0,
"results": "string",
"restBetweenRounds": "string"
},
{
"type": 1,
"results": "string",
"setCount": 0
},
{
"type": 2,
"items": [
{
"type": 0,
"results": "string",
"restBetweenRounds": "string"
},
{
"type": 1,
"results": "string",
"setCount": 0
}
],
"results": "string"
}
]
}
""".trimIndent()
val itemHolder: ItemHolder = mapper.decodeFromString(json)
println(itemHolder)
println(mapper.encodeToString(itemHolder))
}
#Serializable
data class ItemHolder(
val startTime: String,
val clientId: String,
val status: Int,
val items: List<BaseItem>,
)
#Serializable(with = BaseItem.Serializer::class)
sealed class BaseItem {
abstract val type: Int
object Serializer : JsonContentPolymorphicSerializer<BaseItem>(BaseItem::class) {
override fun selectDeserializer(
element: JsonElement
): DeserializationStrategy<out BaseItem> {
return when (val type = element.jsonObject["type"]?.jsonPrimitive?.intOrNull) {
0 -> ItemType0.serializer()
1 -> ItemType1.serializer()
2 -> ItemType2.serializer()
else -> error("unknown Item type $type")
}
}
}
}
#Serializable
data class ItemType0(
val results: String,
val restBetweenRounds: String,
) : BaseItem() {
#EncodeDefault
override val type: Int = 0
}
#Serializable
data class ItemType1(
val results: String,
val setCount: Int,
) : BaseItem() {
#EncodeDefault
override val type: Int = 1
}
#Serializable
data class ItemType2(
val results: String,
val items: List<BaseItem>,
) : BaseItem() {
#EncodeDefault
override val type: Int = 2
}
This prints
ItemHolder(
startTime=2022-07-27T13:32:57.379Z,
clientId=3fa85f64-5717-4562-b3fc-2c963f66afa6,
status=0,
items=[
ItemType0(results=string, restBetweenRounds=string),
ItemType1(results=string, setCount=0),
ItemType2(
results=string,
items=[
ItemType0(results=string, restBetweenRounds=string),
ItemType1(results=string, setCount=0)
]
)
]
)
and
{
"startTime": "2022-07-27T13:32:57.379Z",
"clientId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": 0,
"items": [
{
"results": "string",
"restBetweenRounds": "string",
"type": 0
},
{
"results": "string",
"setCount": 0,
"type": 1
},
{
"results": "string",
"items": [
{
"results": "string",
"restBetweenRounds": "string",
"type": 0
},
{
"results": "string",
"setCount": 0,
"type": 1
}
],
"type": 2
}
]
}
which matches the input!
Versions
Kotlin 1.7.10
Kotlinx Serialization 1.3.4

How to convert Flow<List<Object>> to Flow<Object> Kotlin coroutine?

Given an ObjectA that contains a List of ObjectB from a WebClient request. I'm wanting to return a flow of my DTO from ObjectB
data class MyDto(val id: String, val name: String) {
companion object {
fun from(objectB: ObjectB) {
return MyDto(id = objectB.id, name = objectB.name)
}
}
...
return webClient.get()
.uri(baseUrl)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToFlow<ObjectA>()
.flatMapConcat { response ->
val x = response.objectB.map { MyDto.from(it) }
retrun#flatMapConcat flowOf(x)
}
This returns JSON of Array<Array<MyDto>>
[
[
{
"id": 1,
"name": "Joe"
},
{
"id": 2,
"name": "Jane"
}
]
]
Expected results are Flow<MyDto>
[
{
"id": 1,
"name": "Joe"
},
{
"id": 2,
"name": "Jane"
}
]
flowOf(x) returns Flow<List<ObjectA>>, but you need to return Flow<ObjectA>
...
.flatMapConcat { response ->
response.objectB.asFlow().map { MyDto.from(it) }
}

How to make GroupBy result list to new Map in Webflux

How to make GroupBy result list to new Map in Webflux
There is my input list and expect result. then what should I do to make the result.
// expect
{
"timestamp": "2019-06-13T00:00:00.000",
"result": {
"first": 1,
"second": 2,
"third": 3
}
}
// input list
[
{
"timestamp": "2019-06-13T00:00:00.000",
"first": 1
},
{
"timestamp": "2019-06-13T00:00:00.000",
"second": 2
},
{
"timestamp": "2019-06-13T00:00:00.000",
"third": 3
}
]
val Flux.fromIterable(list)
.groupBy{it.timestamp}
.concatMap { groupItem ->
// here!! I want to make `group by result list to new Map``
Result(timestamp = groupItem.key()!!, Item(first = ?, second = ?, third =?))
}
I figured it out.
Flux.merge(first, second, thrid)
.groupBy { it.timestamp }
.concatMap {
it.map { item ->
val container = mutableMapOf<String, Any>()
if (item is firstEntity) {
container["first"] = item.result.count
container["timestamp"] = it.key()!!
}
if (item is secondEntity) container["second"] = item.result.count
if (item is thridEntity) container["thrid"] = item.result.count
container
}.reduce { acc, current ->
acc.putAll(current)
acc
}
}
.map {
val first = (it["first"] ?: 0) as Int
val second = (it["second"] ?: 0) as Int
val thrid = (it["thrid"] ?: 0) as Int
val timestamp = (it["timestamp"] ?: "") as String
// somthing!!
}

Retrofit 2. How to handle error JSON inside response, if code is 200

I am using Vk Api, and during 1 scenario I am getting a response object with code 200, but body of it's an Error JSON.
I want to ask you - is it possible to somehow get the error object from response and look at the error_code that has been returned from the Vk Api.
I am using Retrofit 2 on android and GsonConverterFactory.
I am trying to do something like this:
class NetworkCheckerInterceptor(val networkChecker: NetworkChecker) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val requestBuilder = chain.request().newBuilder()
if (!networkChecker.isConnected()) {
throw NoNetworkConnectionException("No network connection.")
}
try {
val response = chain.proceed(requestBuilder.build())
val error = Gson().fromJson(response.body()?.string(), Error::class.java)
return response
} catch (e: SocketTimeoutException) {
throw SocketTimeoutException()
} catch (e: UnknownHostException) {
throw UnknownHostException()
}
}
}
But I am getting error when I am trying to get 'Error' object.
Json error example:
{
"error": {
"error_code": 7,
"error_msg": "Permission to perform this action is denied",
"request_params": [
{
"key": "oauth",
"value": "1"
},
{
"key": "method",
"value": "stats.get"
},
{
"key": "timestamp_to",
"value": "1542549195"
},
{
"key": "month",
"value": "month"
},
{
"key": "group_id",
"value": "56461699"
},
{
"key": "v",
"value": "5.87"
},
{
"key": "interval",
"value": "month"
},
{
"key": "stats_groups",
"value": "visitors"
},
{
"key": "timestamp_from",
"value": "1514757660"
}
]
}
}
The only thing I care about is "error_code": 7 it's about permition problem.
So, how can I get this object even if my response code is 200 ?
You can create base class for VK network response
abstract class BaseVkResponse<T> {
var response: T? = null
var error: VKError? = null // (from vk sdk)
}
and each response should extend it. For example
class NewsItem {
var type: String? = null
var text: String? = null
var date: Long? = null
}
class NewsPage {
var items: List<NewsItem>? = null
#SerializedName("nextFrom")
var nextFrom: String? = null
}
class NewsResponse : BaseVkResponse<NewsPage>()
and retrofit interface looks like
interface VkService {
#GET("newsfeed.getRecommended")
fun getNews(#Query("access_token") accessToken: String,
#Query("v") apiVersion: String,
#Query("count") count: Int?,
#Query("start_from") startFrom: String?): Single<NewsResponse>
}
Then register special type adapter to parse VkError type
internal class ErrorTypeAdapter : JsonDeserializer<VKError> {
#Throws(JsonParseException::class)
override fun deserialize(json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): VKError? {
return try {
VKError(JSONObject(json.toString()))
} catch (e: JSONException) {
null
}
}
}
val gson = GsonBuilder().apply {
registerTypeAdapter(VKError::class.java, ErrorTypeAdapter())
}
If response's error field is not null, you should handle it as you wish. In other cases you can treat it as successful.