How to parse below Json data in Kotlin? - kotlin

I need to parse this information-
[
{
"artist": "12",
"image": "23"
},
{
"video_id": "12",
"video_title": "23"
},
{
"video_id": "12",
"video_title": "23"
},
{
"video_id": "12",
"video_title": "23"
},
{
"video_id": "12",
"video_title": "23"
},
{
"video_id": "12",
"video_title": "23"
}]
As you can see the first field is different, how to parse below information differently and the first field differently in Kotlin.
I am parsing 1st part as-
response ->
for (i in 0..(response.length() -1)){
/**
FIRST SONG
**/
val song = response.get(0).toString()
val listOfSongs = response.toString()
val parser = Parser()
val stringBuilder = StringBuilder(song)
val json: JsonObject = parser.parse(stringBuilder) as JsonObject
val firstArtist = json.string("artist")
val firstImage = json.string("image")
val intent = Intent(activity,ResultPage::class.java)
intent.putExtra("firstArtist",firstArtist)
intent.putExtra("firstImage",firstImage)
startActivity(intent)
/**
FIRST SONG
**/
}
}
and am also using KLAXON library here.

For your json, this should work :
fun parseResponse(response: String) {
var artist = ""
var image = ""
val videoList = ArrayList<Video>()
val jsonArray = JSONArray(response)
(0..5).forEach { index ->
val jsonObject = jsonArray.getJSONObject(index)
if (jsonObject.has("artist") && jsonObject.has("image")) {
artist = jsonObject.getString("artist")
image = jsonObject.getString("image")
}
else if (jsonObject.has("video_id") && jsonObject.has("video_title")) {
val newVideo = Video(jsonObject.getString("video_id"), jsonObject.getString("video_title"))
videoList.add(newVideo)
}
}
}
class Video(val id: String, val title: String)
But this way is very lengthy and unnecessary. I would suggest use an Object Mapping library like GSON or Moshi.
For that, the video list in your json should ideally be something like:
[
{
"artist": "12",
"image": "23",
"videos": [
{
"video_id": "12",
"video_title": "23"
},
{
"video_id": "12",
"video_title": "23"
},
{
"video_id": "12",
"video_title": "23"
},
{
"video_id": "12",
"video_title": "23"
},
{
"video_id": "12",
"video_title": "23"
}
]
}
]
Using this Json, you can easily create a class for this object, e.g.
class Artist(val id: String, val name: String, val image: String, val videos: List<Video>)
class Video(#field:Json(name = "video_id") val id: String, #field:Json(name = "video_title") val title: String)
And parse them easily like this:
Moshi.Builder().build().adapter(Artist::class.java).fromJson(response)
and then access this information like:
val artist = Moshi.Builder().build().adapter(Artist::class.java).fromJson(response)
intent.putExtra("firstArtist",artist?.name)
intent.putExtra("firstImage",artist?.image)

You can use below code to parse given json in kotlin
private fun parseJson(jsonResponse: String){
val jsonArray = JSONArray(jsonResponse)
for (i in 0..jsonArray!!.length() - 1) {
val jsonObj = jsonArray.optJSONObject(i)
val artist =jsonObj.optString("artist")
val image =jsonObj.optString("image")
val videosArray = jsonObj.optJSONArray("videos")
for (i in 0..videosArray!!.length() - 1) {
val videoObj = jsonArray.optJSONObject(i)
val video_id =videoObj.optString("video_id")
val video_title =videoObj.optString("video_title")
}
}
}

Related

Kotlin - switching object detail based on group by

For example I have a class with below json format
[
{
"name": "a",
"detail": [
"1",
"2",
"3"
]
},
{
"name": "b",
"detail": [
"2",
"3",
"4"
]
}
]
how to change grouped it based on the detail?
[
{
"detail": "1",
"name": [
"a"
]
},
{
"detail": "2",
"name": [
"a",
"b"
]
},
{
"detail": "3",
"name": [
"a",
"b"
]
},
{
"detail": "4",
"name": [
"b"
]
}
]
below is my class structure
data class funName(
#field:JsonProperty("name")
val name: String = "",
#field:JsonProperty("detail")
val detail: Array<String> = arrayOf(""),
)
and my object is based on the array of funName
val data: Array<funName> = ...
i really have no idea how to do it.
val convert = data.groupBy { x -> x.detail } ??
Is this doable in kotlin/java?
Since the original data is grouped by name, you can think of the original data as a list of pairs
name detail
a 1
a 2
a 3
b 2
b 3
b 4
Mapping it to this format first would make it very easy to group by the second thing (detail) in the pair.
Since each funName corresponds to multiple pairs like this, you should use flatMap on data.
val result = data.flatMap { funName ->
funName.detail.map { funName.name to it }
}
.groupBy(keySelector = { (name, detail) -> detail }, valueTransform = { (name, detail) -> name })
// or more concisely, but less readable
// .groupBy({ it.second }) { it.first }
This will get you a Map<String, List<String>>.
If you want a List<Result>, where Result is something like
data class Result(
val detail: String = "",
val names: List<String> = listOf(),
)
You can add an additional map:
.map { (k, v) -> Result(k, v) }

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 use Retrofit 2 to send object with list of another object with image

I need somebody help for this case:
Here the format of my object :
{
"transport": true,
"fraisTransport": "string"
"cars": [
{
"id": "string",
"prix": "string",
"photo_url1" : "string",
"photo_url2" : "string"
},
{
"id": "string",
"prix": "string",
"photo_url1" : "string",
"photo_url2" : "string"
}
]
}
Here is my Api interface
#Multipart
#POST("declaration")
fun addDeclaration( #Part carsImage: Array<MultipartBody.Part> ,
#Part propertyCars: MultipartBody.Part,
#Part dataDeclaration: RequestBody): Observable<Response>
here how i set the variable for dataDEclaration
val jsonObject = JSONObject()
jsonObject.put("transport", declaration.transport)
jsonObject.put("frais_transport", declaration.fraisTransport)
val bodyDeclarationInfo = jsonObject.toString().toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
Now my problem is how to set the variable for cars objects. Please any suggestions will welcome. Thanks
I adivise you to either send data as a class following this methode : https://futurestud.io/tutorials/retrofit-send-objects-in-request-body
Or you can create your json and send it like this : How to POST raw whole JSON in the body of a Retrofit request?
After long search i got a way to solve my problem. Perhaps that can help somebody.
#Multipart
#POST("declaration")
fun addDeclaration(#Part carsImage: MutableList<MultipartBody.Part>, #PartMap
declaration: HashMap<String?, RequestBody?>?): Observable<Response>
val declarationInfo: HashMap<String?, RequestBody?>? = HashMap()
declarationInfo["transport"] = createPartFromString("" + declarationVente.transport)
task["fraisTransport"] = createPartFromString("" +declarationVente.fraisTransport)
val body: MutableList<MultipartBody.Part> = mutableListOf()
val listA: List<Cars> = mutableListOf<Cars>().apply {
for (aD in 0 until it.size) {
declarationInfo["cars[$aD][id]"] = createPartFromString(it[aD].animalId.toString())
declarationInfo["cars[$aD][prix]"] = createPartFromString(it[aD].prixDeclaration.toString())
val photoFile1 = File(it[aD].photo1)
val reqFile = photoFile1.asRequestBody("image/*".toMediaTypeOrNull())
val imageFile1 = MultipartBody.Part.createFormData("cars[$aD][uploadFile1]", photoFile1.name, reqFile)
body.add(imageFile1)
}
}

How to read a subsection from a firebase database

I am using this code to access a Firebase Database, but I am cannot figure out how to read the sub array of strings. (the JSON structure is below)). The code I have returns the top level items, but not the list of strings. Would someone be able to assist with this issue?
Here is my function to read from the DB:
func sizes(userId: String = Auth.auth().currentUser!.uid, success: #escaping ([Sizes]) -> ()) {
let ref = Router.sizes.reference()
let query = ref.queryOrdered(byChild: "name") //userId)
query.observe(.value, with: { snapshot in
var array = [Sizes]()
for child in snapshot.children {
if let size = Mapper<Sizes>().map(JSON: (child as! DataSnapshot).value as! [String : AnyObject]) {
array.append(size)
}
}
success(array)
})
}
My Firebase JSON is as follows:
{
"-SzCat_001": {
"name": "Womans",
"sizeCategories": {
"name": "Pants",
"sizeDescriptor": [
"00",
"0",
"2",
"4",
"6",
"8",
"10",
"12",
"XL"
]
}
}
}
And this is what I get returned?
[0] = {
name = "Womans"
sizeCategories = 0 values {} }
I am trying to figure out how to read the sizeCategories list of strings as a subarray of sizes.
Here is my definition of sizes and sizeCategories:
struct Sizes: Mappable {
var name: String = ""
var sizeCategories = [SizeCategories]()
init() {
}
init?(map: Map) {
}
mutating func mapping(map: Map) {
name <- map["name"]
sizeCategories <- map["sizeCategories"]
}
}
struct SizeCategories: Mappable {
var name: String = ""
var sizeDescriptor = [String]()
init() {
}
init?(map: Map) {
}
mutating func mapping(map: Map) {
name <- map["name"]
sizeDescriptor <- map["sizeDescriptor"]
}
}
Thanks for any help!!!
You're jumping through a lot of hoops to read the data here. You could just let allMyData = snapshot.value as! [String: AnyObject] and know that each internal value is also a [String: AnyObject]. But if you really want to destructure into something more typed with this mapping technique, have a look at your sizes definition:
var sizeCategories = [SizeCategories]()
This says "sizeCategories is an array of SizeCategories type". But your data is not structured as an array, it is a dict:
"sizeCategories": {
"name": "Pants",
"sizeDescriptor": [
"00",
"0",
"2",
"4",
"6",
"8",
"10",
"12",
"XL"
]
}
You need to adjust your definition and mapping method here for this field.