Is there an easy method in Kotlin to allow me to convert a Map to an arbitrary data class?
The Map I'm trying to convert has all of the keys of the required fields of the data class.
I've hunted around, but haven't been able to find anything that seems to talk about doing this in a very generic way.
I know that I can use ObjectMapper
, but that requires an extra library. Looking for something that is available just with Kotlin.
Why not just use a map delegate?
class MyData(val map: Map<String, String>) {
val foo by map
val bar by map
}
Or you can wrap it using a companion object and call it using MyData.from(mymap)
data class MyData(val foo: String, val bar: String) {
companion object {
fun from(map: Map<String, String>) = object {
val foo by map
val bar by map
val data = MyData(foo, bar)
}.data
}
Maybedelegated properties is something for you. That way you can also just adapt the map. That however also allows adding keys and values which aren't 1:1 mappable to your class properties, eg:
class YourData {
val backedMap = mutableMapOf<String, String>()
var someProp by backedMap
}
val obj = YourData().apply {
someProp = "value"
}
Now if backedMap
is visible, then the following can also be added, even though there does not exist any such property:
obj.backedMap["unknown prop"] = "some other value"
On the other hand however the same map can be used to automatically fill up your fields:
val sourceMap = mutableMapOf("someProp" to "newValue") // the map you want to use to automatically fill up your data class
sourceMap.forEach { key, newValue -> obj.backedMap[key] = newValue }
Another way is to "just" use reflection. That way you omit the backing map and rather just scan the properties instead, eg:
YourData::class.memberProperties.asSequence()
.filterIsInstance<KMutableProperty<*>>()
.filter { it.name in sourceMap.keys }
.forEach { prop ->
prop.setter.call(obj, sourceMap[prop.name])
}
using kotlin reflection to map constructor parameters to map values this generic function will work for any class that receives field data in the primary constructor
Works well with JSON dbs like firebase and was tested with Map objects from Firebase DocumentSnapshot.data
fun <T : Any> mapToObject(map: Map<String, Any>, clazz: KClass<T>) : T {
//Get default constructor
val constructor = clazz.constructors.first()
//Map constructor parameters to map values
val args = constructor
.parameters
.map { it to map.get(it.name) }
.toMap()
//return object from constructor call
return constructor.callBy(args)
}
with some class
data class Person(val firs: String, val last: String)
use like this
mapToObject(map, Person::class)
Though this is coming late, but it may help someone like it just helped me.
I reviewed @killjoy 's comment and immediately I was mind struck that my honest answer had being on my lap since.
Answer: Using any Json libary eg GSON() or JSon Kotlin serilization Libary, this problem can be solved.
This is a generic example:
allUploadsLiveData.observe(viewLifecycleOwner, {
val userUploads = ((it.value as HashMap<*, *>).values.toList())
for (upload in userUploads){
val product = ((upload as HashMap<*, *>).values.toList()[0] as HashMap<*, *>)
// Use Gson() to convert to Json format
val json = Gson().toJson(product)
try {
//Use Gson() to convert to Your custom class. For me here is ProductData
val data = Gson().fromJson(json, ProductData::class.java)
println("This is all the new data *********************************** ${data.productImageUrl}")
} catch (e: Exception){
println("Exception *************** $${e.printStackTrace()}")
}
}
})
Don't forget to add Gson() dependency version 2.9.0 is the most resent version at this time of my response. Replace the version with the most rest from Google Gson
implementation "com.google.code.gson:gson:2.8.8"
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.