Transform nested data class to single level Map in Kotlin - kotlin

Basically I have a data class like
data class House(
val bedroom: Bedroom,
val livingroom: Livingroom
)
data class Bedroom(
val bed: String,
val pillows: List<String>
)
data class Livingroom(
val sofa: String,
val tv: String
)
I wonder how to map the House data model to a map with only one level:
{
bed = "some-bed",
pillows = ["pillow-a", "pillow-b", "pillow-c"],
sofa = "some-sofa",
tv = "some-tv"
}
I tried gson by first transforming it to a json then to map, but it will give me nested maps

Related

How can I convert a data class to a room entity class and a UI class?

I'm pulling values ​​like conversion_rates from the exchange api and I don't want to use all of these values. Because there are data that I do not want to use in the data coming from the api. In addition, I want to save the data I want to use in the room and perform operations such as reading and updating from there. But I couldn't find how to convert the data class to room entity and the data class I want to show in ui.
The data in the api is as follows, for example. This example uses USD currency
{
"result":"success",
"documentation":"https://www.exchangerate-api.com/docs",
"terms_of_use":"https://www.exchangerate-api.com/terms",
"time_last_update_unix":1669852801,
"time_last_update_utc":"Thu, 01 Dec 2022 00:00:01 +0000",
"time_next_update_unix":1669939201,
"time_next_update_utc":"Fri, 02 Dec 2022 00:00:01 +0000",
"base_code":"USD",
"conversion_rates":{
"USD":1,
"AED":3.6725,
"AFN":88.4843,
"ALL":112.6064,
"AMD":395.1203,
"ANG":1.7900,
"AOA":509.9729,
"ARS":166.6930,
"AUD":1.4810,
"AWG":1.7900,
"AZN":1.6987,
"BAM":1.8843,
"BBD":2.0000,
"BDT":101.1341,
"BGN":1.8844,
"BHD":0.3760,
"BIF":2059.0872,
"BMD":1.0000,
"BND":1.3652,
"BOB":6.9223,
"BRL":5.2810,
"BSD":1.0000,
"BTN":81.2806,
"BWP":12.8706,
"BYN":2.8013,
"BZD":2.0000,
"CAD":1.3488,
"CDF":2054.0886,
"CHF":0.9472,
"CLP":900.6901,
"CNY":7.0769,
"COP":4807.5892,
"CRC":599.5200,
"CUP":24.0000,
"CVE":106.2304,
"CZK":23.4794,
"DJF":177.7210,
"DKK":7.1874,
"DOP":54.5873,
"DZD":138.5566,
"EGP":24.5835,
"ERN":15.0000,
"ETB":53.4589,
"EUR":0.9631,
"FJD":2.2145,
"FKP":0.8319,
"FOK":7.1874,
"GBP":0.8317,
"GEL":2.7155,
"GGP":0.8319,
"GHS":14.2807,
"GIP":0.8319,
"GMD":63.3901,
"GNF":8622.0204,
"GTQ":7.8226,
"GYD":209.1401,
"HKD":7.8054,
"HNL":24.6994,
"HRK":7.2588,
"HTG":140.6488,
"HUF":393.5594,
"IDR":15633.2624,
"ILS":3.4355,
"IMP":0.8319,
"INR":81.2818,
"IQD":1458.8962,
"IRR":42024.7114,
"ISK":141.8861,
"JEP":0.8319,
"JMD":154.0163,
"JOD":0.7090,
"JPY":138.2686,
"KES":122.8172,
"KGS":84.4061,
"KHR":4125.2930,
"KID":1.4813,
"KMF":473.9665,
"KRW":1311.7736,
"KWD":0.2996,
"KYD":0.8333,
"KZT":469.2553,
"LAK":17329.4289,
"LBP":1507.5000,
"LKR":363.3087,
"LRD":153.9036,
"LSL":17.0637,
"LYD":4.8894,
"MAD":10.7013,
"MDL":19.4034,
"MGA":4347.5707,
"MKD":59.4442,
"MMK":2452.0310,
"MNT":3422.5815,
"MOP":8.0398,
"MRU":38.0126,
"MUR":43.6263,
"MVR":15.4107,
"MWK":1029.7547,
"MXN":19.2883,
"MYR":4.4641,
"MZN":64.0629,
"NAD":17.0637,
"NGN":443.7037,
"NIO":36.3802,
"NOK":9.8760,
"NPR":130.0490,
"NZD":1.5947,
"OMR":0.3845,
"PAB":1.0000,
"PEN":3.8417,
"PGK":3.5202,
"PHP":56.3904,
"PKR":223.9704,
"PLN":4.4951,
"PYG":7228.9088,
"QAR":3.6400,
"RON":4.7382,
"RSD":113.3592,
"RUB":60.9665,
"RWF":1099.6940,
"SAR":3.7500,
"SBD":8.1340,
"SCR":13.0123,
"SDG":568.2933,
"SEK":10.5247,
"SGD":1.3649,
"SHP":0.8319,
"SLE":18.6210,
"SLL":18620.9957,
"SOS":568.2695,
"SRD":30.9539,
"SSP":643.6471,
"STN":23.6035,
"SYP":2502.5362,
"SZL":17.0637,
"THB":35.2322,
"TJS":10.1162,
"TMT":3.4979,
"TND":3.1050,
"TOP":2.3745,
"TRY":18.6339,
"TTD":6.7542,
"TVD":1.4813,
"TWD":30.6481,
"TZS":2333.7110,
"UAH":36.4908,
"UGX":3747.1520,
"UYU":39.4702,
"UZS":11228.0193,
"VES":11.0789,
"VND":24625.3969,
"VUV":121.1513,
"WST":2.7337,
"XAF":631.9553,
"XCD":2.7000,
"XDR":0.7612,
"XOF":631.9553,
"XPF":114.9656,
"YER":250.0464,
"ZAR":17.0568,
"ZMW":17.0409,
"ZWL":653.4299
}
}
but here, for example, I don't want to save
time_next_update_utc,
"time_next_update_unix",
"time_last_update_utc,
time_last_update_unix,
terms_of_use",
documentation
data to the room, and I don't want to show them to the user either.
because data is useless to me. I just want to save the following data in the room and show it to the user
"result",
"base_code",
"conversion_rates"
and I prepared something like that.
Room
Entity
#Entity(tableName = "ExchangeValues")
data class ExchangeEntity(
#ColumnInfo(name = "allData") val allData: AllData,
#PrimaryKey(autoGenerate = true) val uid:Int?=null
)
Dao
#Dao
interface ExchangeDao {
#Query("SELECT * FROM ExchangeValues")
suspend fun getAll() : List<ExchangeEntity>
#Query("UPDATE ExchangeValues SET ")
suspend fun update()
}
Remote
Dto
#Serializable
data class AllData(
val allData:List<ExchangeDto>
)
#Serializable
data class ExchangeDto(
val base_code: String,
val conversion_rates: ConversionRates,
val documentation: String,
val result: String,
val terms_of_use: String,
val time_last_update_unix: Int,
val time_last_update_utc: String,
val time_next_update_unix: Int,
val time_next_update_utc: String
)
#Serializable
data class ConversionRates(
val conversionRates : Map<String,Double>
)
In this Dto, I wrote extension functions such as toEntity to convert it to an entity or to convert it to a model class that I will display ui, but I couldn't make it up. I'm a little lacking in this
Repository
interface ExchangeRepository {
suspend fun getAll() : Flow<List<AllData>>
suspend fun update(exchange: ExchangeDto)
}
RepositoryImpl
class ExchangeRepositoryImpl #Inject constructor(
private val dao:ExchangeDao
) : ExchangeRepository{
override suspend fun getAll() : Flow<List<AllData>> {
return flow {
emit(dao.getAll().map {it.allData}) // this part is wrong
}
}
override suspend fun update(exchange: ExchangeDto) {
dao.update(exchange.base_code,exchange.result,exchange.conversion_rates) // While updating, it is important that it is compatible with my entity class. There is also a problem here.
}
}
I guess I didn't prepare the dto properly. Actually, I'm sorry that I may have shared the codes a bit incompletely and badly because I don't know exactly what to do, but I hope you understand what I want to do.
The problem is when I getAll in the ExchangeRepositoryImpl , the ExchangeEntity is constantly coming, and I can't translate it either. At the same time, I couldn't quite figure out how to adapt the update in ExchangeRepositoryImpl to them. because i need to update data every 24 hours according to data in api.

How to add data to Map copying existing values based on List of identifiers

Sorry for the poor title but it is rather hard to describe my use case in a short sentence.
Context
I have the following model:
typealias Identifier = String
data class Data(val identifier: Identifier,
val data1: String,
val data2: String)
And I have three main data structures in my use case:
A Set of Identifiers that exist and are valid in a given context. Example:
val existentIdentifiers = setOf("A-1", "A-2", "B-1", "B-2", "C-1")
A Map that contains a List of Data objects per Identifier. Example:
val dataPerIdentifier: Map<Identifier, List<Data>> = mapOf(
"A-1" to listOf(Data("A-1", "Data-1-A", "Data-2-A"), Data("A-1", "Data-1-A", "Data-2-A")),
"B-1" to listOf(Data("B-1", "Data-1-B", "Data-2-B")),
"C-1" to listOf(Data("C-1", "Data-1-C", "Data-2-C"))
)
A List of Lists that group together the Identifiers that should share the same List<Data> (each List includes always 2 Identifiers). Example
val identifiersWithSameData = listOf(listOf("A-1", "A-2"), listOf("B-1", "B-2"))
Problem / Use Case
The problem that I am trying to tackle stems from the fact that dataPerIdentifier might not contain all identifiersWithSameData given that existentIdentifiers contains such missing Identifiers. I need to add those missing Identifier to dataPerIdentifier, copying the List<Data> already in there.
Example
Given the data in the Context section:
A-1=[Data(identifier=A-1, data1=Data-1-A, data2=Data-2-A),
Data(identifier=A-1, data1=Data-1-A, data2=Data-2-A)],
B-1=[Data(identifier=B-1, data1=Data-1-B, data2=Data-2-B)],
C-1=[Data(identifier=C-1, data1=Data-1-C, data2=Data-2-C)]
The desired outcome is to update dataPerIdentifier so that it includes:
A-1=[Data(identifier=A-1, data1=Data-1-A, data2=Data-2-A),
Data(identifier=A-1, data1=Data-1-A, data2=Data-2-A)],
B-1=[Data(identifier=B-1, data1=Data-1-B, data2=Data-2-B)],
C-1=[Data(identifier=C-1, data1=Data-1-C, data2=Data-2-C)],
A-2=[Data(identifier=A-2, data1=Data-1-A, data2=Data-2-A),
Data(identifier=A-2, data1=Data-1-A, data2=Data-2-A)]
The reason is that existentIdentifiers contains A-2 that is missing in the initial dataPerIdentifier Map. B-2 is also missing in the initial dataPerIdentifier Map but existentIdentifiers does not contain it, so it is ignored.
Possible solution
I have already a working code (handleDataForMultipleIdentifiers() method is the one doing the heavy lifting), but it does not feel to be the cleanest or easiest to read:
fun main(args: Array<String>) {
val existentIdentifiers = setOf("A-1", "A-2", "B-1", "C-1")
val dataPerIdentifier: Map<Identifier, List<Data>> = mapOf(
"A-1" to listOf(Data("A-1", "Data-1-A", "Data-2-A"), Data("A-1", "Data-1-A", "Data-2-A")),
"B-1" to listOf(Data("B-1", "Data-1-B", "Data-2-B")),
"C-1" to listOf(Data("C-1", "Data-1-C", "Data-2-C"))
)
val identifiersWithSameData = listOf(listOf("A-1", "A-2"), listOf("B-1", "B-2"))
print("Original Data")
println(dataPerIdentifier)
print("Target Data")
println(dataPerIdentifier.handleDataForMultipleIdentifiers(identifiersWithSameData, existentIdentifiers))
}
fun Map<Identifier, List<Data>>.handleDataForMultipleIdentifiers(identifiersWithSameData: List<List<Identifier>>, existentIdentifiers: Set<Identifier>)
: Map<Identifier, List<Data>> {
val additionalDataPerIdentifier = identifiersWithSameData
.mapNotNull { identifiersList ->
val identifiersWithData = identifiersList.find { it in this.keys }
identifiersWithData?.let { it to identifiersList.minus(it).filter { it in existentIdentifiers } }
}.flatMap { (existentIdentifier, additionalIdentifiers) ->
val existentIdentifierData = this[existentIdentifier].orEmpty()
additionalIdentifiers.associateWith { identifier -> existentIdentifierData.map { it.copy(identifier = identifier) } }.entries
}.associate { it.key to it.value }
return this + additionalDataPerIdentifier
}
typealias Identifier = String
data class Data(val identifier: Identifier,
val data1: String,
val data2: String)
So my question is: how can I do this in a simpler way?
If identifiersWithSameData always contains 2 identifiers per item then it should not really be a list of lists, but rather a list of pairs or dedicated data classes. And if you convert this data structure into a map like this:
val identifiersWithSameData = mapOf("A-1" to "A-2", "A-2" to "A-1", "B-1" to "B-2", "B-2" to "B-1")
The the whole solution is pretty simple:
existentIdentifiers.associateWith {
dataPerIdentifier[it] ?: dataPerIdentifier[identifiersWithSameData[it]!!]!!
}
I'm not sure about both !!, for example I don't know if it is guaranteed that identifier existing in existentIdentifiers exists in identifiersWithSameData as well. You may need to tune this solution a little.

Is there a way to lazy-init a costly field in a specific constructor?

I have a data class I want to populate, where in one constructor I have the data already, and in another I would like to fetch it only when it is required, which is rare.
A sample code would be:
data class Source1(val str1: String)
data class Source2(val str2: String)
data class DTO(val data1: String, val data2: String) {
// ctor which does not need laziness
constructor(source1: Source1) : this(
data1 = source1.str1,
data2 = source1.str1
)
// ctor which needs costly data
constructor(source2: Source2, costlyData: String) : this(
data1 = source2.str2,
data2 = costlyData
)
}
fun demo() {
val source1 = Source1("some str - 1")
DTO(source1)
val source2 = Source2("some str - 2")
val costlyData: String = costlyOperation() // this is the operation I'd like to execute lazily
DTO(source2, costlyData)
}
I'd say the easiest way would be to accept a function as a constructor parameter, something like this:
class DTO(provider:()->String){
constructor(data: String):this({data})
val data by lazy{ provider()}
}
So you can use it in both ways:
val eager = DTO("some str - 1")
val lazy = DTO(::costlyOperation)
A bit nicer way is to have a Source abstraction with different implementations for providing a constant value and performing an operation. But the overall idea would be the same.
Although I wouldn't call this DTO anymore and it loses its data class capabilities regarding the content.

Android room many-to-one relationship

I cannot find anything about this type of relation (everything is about one-to-one, one-to-many or many-to-many). And even those look a little bit too complicated for what I need.
I have a table with tasks and a table with images. Multiple tasks can have the same image in order to save space (the image is not deleted when the task is deleted). I have an entity for the task
import android.graphics.Bitmap
import androidx.room.*
#Entity(tableName = "tasks")
class Task(
#PrimaryKey(autoGenerate = true)
val id: Long,
val imageId: Long = 0,
// this needs a foreign key to Image
val image: Image
)
and another one for the image
import androidx.room.ColumnInfo
import androidx.room.PrimaryKey
#Entity(tableName = "images")
class Image (
#PrimaryKey(autoGenerate = true)
val id: Long,
val title: String,
#ColumnInfo(typeAffinity = ColumnInfo.BLOB)
val data: ByteArray? = null
)
How do I add a foreignKey for the imageId column so that it points to an Image? Can I directly obtain an Image object as a member of Task without having to create another class?
You have to use one-to-many relationship here. There is an example from d.android.com. If you will do like that you will have a Task entity, an Image entity and data class TaskWithImage, just like in the example provided.
data class TaskWithImage(
#Embedded val task: Task,
#Embedded(prefix = "img_") val image: Image
)
#Query("""
SELECT * FROM tasks
INNER JOIN images as img_ ON tasks.id = img_.id
""")
fun getTasksWithImage(): List<TaskWithImage>

get a list of parsed json elements

I parsed a json string to the following object structure using gson:
data class Base (
val expand: String,
val startAt: Long,
val maxResults: Long,
val total: Long,
val issues: List<Issue>
)
data class Issue (
val expand: String,
val id: String,
val self: String,
val key: String,
val fields: Fields
)
data class Fields (
val summary: String,
val issuetype: Issuetype,
val customfield10006: Long? = null,
val created: String,
val customfield11201: String? = null,
val status: Status,
val customfield10002: Customfield10002? = null,
val customfield10003: String? = null
)
Everything works fine and also the object model is correct, because I can access each element of the object.
However, I encountered the problem that I dont know how to get a list of all field-elements. Right now, I have only figured out how to access one item (by using an index and get()-function):
val baseObject = gson.fromJson(response, Base::class.java)
val fieldsList = baseObject.issues.get(0).fields
I actually want to have a list of all field elements and not just one. Is there a gson function allowing me to do that? I couldn't find anything about it in the gson documentation for java.
You don't have to look for some gson function when you've already created a baseObject. You just need to get from each issue it's fields and you can use a map function to achieve this, it will convert each issue to a new type so you can get issue fields there
val fieldFromAllIssues: List<Fields> = baseObject.issues.map { it.fields }
it in this context is a one issue. More explanation about it is here