How to convert a Kotlin data class object to map?
Solution 1
I was using the jackson method, but turns out the performance of this is terrible on Android for first serialization (github issue here). And its dramatically worse for older android versions, (see benchmarks here)
But you can do this much faster with Gson. Conversion in both directions shown here:
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
val gson = Gson()
//convert a data class to a map
fun <T> T.serializeToMap(): Map<String, Any> {
return convert()
}
//convert a map to a data class
inline fun <reified T> Map<String, Any>.toDataClass(): T {
return convert()
}
//convert an object of type I to type O
inline fun <I, reified O> I.convert(): O {
val json = gson.toJson(this)
return gson.fromJson(json, object : TypeToken<O>() {}.type)
}
//example usage
data class Person(val name: String, val age: Int)
fun main() {
val person = Person("Tom Hanley", 99)
val map = mapOf(
"name" to "Tom Hanley",
"age" to 99
)
val personAsMap: Map<String, Any> = person.serializeToMap()
val mapAsPerson: Person = map.toDataClass()
}
Solution 2
This extension function uses reflection, but maybe it'll help someone like me coming across this in the future:
inline fun <reified T : Any> T.asMap() : Map<String, Any?> {
val props = T::class.memberProperties.associateBy { it.name }
return props.keys.associateWith { props[it]?.get(this) }
}
Solution 3
I have the same use case today for testing and ended up i have used Jackson object mapper to convert Kotlin data class into Map. The runtime performance is not a big concern in my case. I haven't checked in details but I believe it's using reflection under the hood but it's out of concern as happened behind the scene.
For Example,
val dataclass = DataClass(p1 = 1, p2 = 2)
val dataclassAsMap = objectMapper.convertValue(dataclass, object:
TypeReference<Map<String, Any>>() {})
//expect dataclassAsMap == mapOf("p1" to 1, "p2" to 2)
Solution 4
kotlinx.serialization has an experimental Properties format that makes it very simple to convert Kotlin classes into maps and vice versa:
@ExperimentalSerializationApi
@kotlinx.serialization.Serializable
data class Category constructor(
val id: Int,
val name: String,
val icon: String,
val numItems: Long
) {
// the map representation of this class
val asMap: Map<String, Any> by lazy { Properties.encodeToMap(this) }
companion object {
// factory to create Category from a map
fun from(map: Map<String, Any>): Category =
Properties.decodeFromMap(map)
}
}
Solution 5
The closest you can get is with delegated properties stored in a map.
Example (from link):
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
Using this with data classes may not work very well, however.
Mac
Updated on July 12, 2022Comments
-
Mac almost 2 years
Is there any easy way or any standard library method to convert a Kotlin data class object to a map/dictionary of its properties by property names? Can reflection be avoided?
-
EpicPandaForce about 6 yearsHis problem is that he wants this the other way around: turn a
data class
intoMap<String, Any?>
. -
apetranzilla about 6 years@EpicPandaForce I'm aware, but there's no non-reflective way to do that in Kotlin. The closest alternative would be to use delegated properties in a map. (i.e. you'd assign values to the map in the constructor, rather than properties)
-
EpicPandaForce about 6 yearsYes of course, I'm pretty sure reflection is required.
-
Simon Forsberg over 4 yearsNice solution, although just a note: This does not look like it will be working for nested data structures.
-
Ahsan Naseem over 3 yearsusing above i am getting
No type arguments expected for class TypeReference
using jackson 2.10.0 -
Serge Tahé over 3 yearsHow to use these functions ? Can you show some code ?
-
Tom Hanley over 3 yearssure, I've added some example usage to the answer
-
Serge Tahé over 3 yearsI tried your code and I got two nulls for [personAsMap] and [mapAsPerson] as i got before my comment. That's why i asked some examples. Is this code working for you ? I am using Kotlin 1.4.0
-
Tom Hanley over 3 yearsIts definitely working fine for me. How are you trying to run it? Do you have gson in your class path? I've added the gson imports if that helps. I'm using kotlin 1.4 also and gson 2.8.6, but i don't think this is a version issue.
-
Serge Tahé over 3 yearsHi Tom, i wanted to show the code executed. So i put it in an answer to the original question. Comments are to short for this.
-
Tom Hanley over 3 yearsOk I tried out your code and got the same. Debugging, gson doesn't seem to work properly on local classes. If you move the person class declaration outside of your function, so its a top level class, it works.
-
Jeremy Jao over 3 yearsI don't recommend this solution. GSON doesn't work properly in Kotlin since it's unaware of its null vs. non-null Kotlin compile-time rules. This is a better solution but doesn't handle nested properties, only primitives: stackoverflow.com/a/59316850/4425374
-
Nalawala Murtuza about 3 yearsin my case int convert to double example 1 -> 1.0
-
Nalawala Murtuza about 3 yearsDo you have a solution for nested data structure?
-
Nalawala Murtuza about 3 yearsnested class int property converts to float property.
-
Andrew about 3 yearsUnresolved reference "memberProperties". It is now called "members"
-
bugs_ almost 3 years
No type arguments expected for class TypeReference
be sure, that you are using classcom.fasterxml.jackson.core.type.TypeReference
if you import for exampleorg.springframework.asm.TypeReference
you will get this message :) -
Rescribet almost 3 yearsThis also accounts for @SerialName annotations
-
erksch over 2 yearsThis seems to work only if the data class is flat and has no properties that are lists or maps.