How to convert a Kotlin data class object to map?

前端 未结 6 1520
一个人的身影
一个人的身影 2020-12-09 02:39

Is there any easy way or any standard library method to convert a Kotlin data class object to a map/dictionary of it\'s properties by property names? Can reflection be avoid

相关标签:
6条回答
  • 2020-12-09 03:21

    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.

    0 讨论(0)
  • 2020-12-09 03:24

    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()
    }
    
    0 讨论(0)
  • 2020-12-09 03:25

    @Tom Hanley

    I tried your solution in the following code:

    package tmp
    
    import com.google.gson.Gson
    import com.google.gson.reflect.TypeToken
    
    // https://stackoverflow.com/questions/49860916/how-to-convert-a-kotlin-data-class-object-to-map
    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)
    }
    
    fun main() {
      //example usage
      data class Person(val name: String, val age: Int)
    
      val person = Person("Tom Hanley", 99)
    
      val map = mapOf(
        "name" to "Tom Hanley",
        "age" to 99
      )
    
      val personAsMap: Map<String, Any> = person.serializeToMap()
      println(personAsMap)
    
      val mapAsPerson: Person = map.toDataClass()
      println(mapAsPerson)
    }
    

    When executed I got two nulls in the console. I used Kotlin 1.4 and Gson 2.8.6. I am rather new to Kotlin and perhaps I am doing an error somewhere.

    0 讨论(0)
  • 2020-12-09 03:26

    Kpropmap is a reflection-based library that attempts to make working with Kotlin data classes and Maps easier. It has the following capabilities that are relevant:

    • Can transform maps to and from data classes, though note if all you need is converting from a data class to a Map, just use reflection directly as per @KenFehling's answer.
    data class Foo(val a: Int, val b: Int)
    
    // Data class to Map
    val propMap = propMapOf(foo)
    
    // Map to data class
    val foo1 = propMap.deserialize<Foo>()
    
    • Can read and write Map data in a type-safe way by using the data class KProperty's for type information.

    • Given a data class and a Map, can do other neat things like detect changed values and extraneous Map keys that don't have corresponding data class properties.

    • Represent "partial" data classes (kind of like lenses). For example, say your backend model contains a Foo with 3 required immutable properties represented as vals. However, you want to provide an API to patch Foo instances. As it is a patch, the API consumer will only send the updated properties. The REST API layer for this obviously cannot deserialize directly to the Foo data class, but it can accept the patch as a Map. Use kpropmap to validate that the Map has the correct types, and apply the changes from the Map to a copy of the model instance:

    data class Foo(val a: Int, val b: Int, val c: Int)
    
    val f = Foo(1, 2, 3)
    val p = propMapOf("b" to 5)
    
    val f1 = p.applyProps(f) // f1 = Foo(1, 5, 3)
    

    Disclaimer: I am the author.

    0 讨论(0)
  • 2020-12-09 03:32

    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)
    
    0 讨论(0)
  • 2020-12-09 03:36

    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) }
    }
    
    0 讨论(0)
提交回复
热议问题