将Kotlin数据对象映射到数据对象的更好方法 [英] Better way to map Kotlin data objects to data objects

查看:1672
本文介绍了将Kotlin数据对象映射到数据对象的更好方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想将一些数据类对象转换/映射到类似的数据类对象。例如,Web表单的类到数据库记录的类。

I want to convert/map some "data" class objects to similar "data" class objects. For example, classes for web form to classes for database records.

data class PersonForm(
    val firstName: String,
    val lastName: String,
    val age: Int,
    // maybe many fields exist here like address, card number, etc.
    val tel: String
)
// maps to ...
data class PersonRecord(
    val name: String, // "${firstName} ${lastName}"
    val age: Int, // copy of age
    // maybe many fields exist here like address, card number, etc.
    val tel: String // copy of tel
)

我在Java中使用ModelMapper进行此类工作,但由于数据类是最终的,因此无法使用它(ModelMapper创建CGLib代理以读取映射定义) )。当我们打开这些类/字段时,我们可以使用ModelMapper,但我们必须手动实现data类的功能。
(参见ModelMapper示例: https://github.com/jhalterman/modelmapper/blob/master/examples/src/main/java/org/modelmapper/gettingstarted/GettingStartedExample.java

I use ModelMapper for such works in Java, but it can't be used because data classes are final (ModelMapper creates CGLib proxies to read mapping definitions). We can use ModelMapper when we make these classes/fields open, but we must implement features of "data" class manually. (cf. ModelMapper examples: https://github.com/jhalterman/modelmapper/blob/master/examples/src/main/java/org/modelmapper/gettingstarted/GettingStartedExample.java)

如何在Kotlin中映射这样的数据对象?

How to map such "data" objects in Kotlin?

更新:
ModelMapper自动映射没有映射声明的具有相同名称的字段(如tel - > tel)。我想用Kotlin的数据类来做。

Update: ModelMapper automatically maps fields that have same name (like tel -> tel) without mapping declarations. I want to do it with data class of Kotlin.

更新:
每个类的目的取决于应用程序的类型,但这些可能放在应用程序的不同层。

Update: The purpose of each classes depends on what kind of application, but these are probably placed in the different layer of an application.

例如:


  • 数据库(数据库实体)中的数据到HTML表单的数据(模型/视图模型)

  • REST API结果数据库数据

这些类很相似,但不一样。

These classes are similar, but are not the same.

由于以下原因,我想避免正常的函数调用:

I want to avoid normal function calls for these reasons:


  • 这取决于参数的顺序。具有相同类型(如String)的许多字段的类的函数将很容易被破坏。

  • 尽管大多数映射都可以通过命名约定来解决,但许多声明都是必需的。

当然,有一个具有类似功能的库,但也欢迎Kotlin功能的信息(如在ECMAScript中传播)。

Of course, a library that has similar feature is intended, but information of the Kotlin feature is also welcome (like spreading in ECMAScript).

推荐答案


  1. 最简单(最好?):

  1. Simplest (best?):

fun PersonForm.toPersonRecord() = PersonRecord(
        name = "$firstName $lastName",
        age = age,
        tel = tel
)


  • 反思(表现不佳):

  • Reflection (not great performance):

    fun PersonForm.toPersonRecord() = with(PersonRecord::class.primaryConstructor!!) {
        val propertiesByName = PersonForm::class.memberProperties.associateBy { it.name }
        callBy(args = parameters.associate { parameter ->
            parameter to when (parameter.name) {
                "name" -> "$firstName $lastName"
                else -> propertiesByName[parameter.name]?.get(this@toPersonRecord)
            }
        })
    }
    


  • 缓存反射(性能良好但不如#1快):

  • Cached reflection (okay performance but not as fast as #1):

    open class Transformer<in T : Any, out R : Any>
    protected constructor(inClass: KClass<T>, outClass: KClass<R>) {
        private val outConstructor = outClass.primaryConstructor!!
        private val inPropertiesByName by lazy {
            inClass.memberProperties.associateBy { it.name }
        }
    
        fun transform(data: T): R = with(outConstructor) {
            callBy(parameters.associate { parameter ->
                parameter to argFor(parameter, data)
            })
        }
    
        open fun argFor(parameter: KParameter, data: T): Any? {
            return inPropertiesByName[parameter.name]?.get(data)
        }
    }
    
    val personFormToPersonRecordTransformer = object
    : Transformer<PersonForm, PersonRecord>(PersonForm::class, PersonRecord::class) {
        override fun argFor(parameter: KParameter, data: PersonForm): Any? {
            return when (parameter.name) {
                "name" -> with(data) { "$firstName $lastName" }
                else -> super.argFor(parameter, data)
            }
        }
    }
    
    fun PersonForm.toPersonRecord() = personFormToPersonRecordTransformer.transform(this)
    


  • 在地图中存储属性

    data class PersonForm(val map: Map<String, Any?>) {
        val firstName: String   by map
        val lastName: String    by map
        val age: Int            by map
        // maybe many fields exist here like address, card number, etc.
        val tel: String         by map
    }
    
    // maps to ...
    data class PersonRecord(val map: Map<String, Any?>) {
        val name: String    by map // "${firstName} ${lastName}"
        val age: Int        by map // copy of age
        // maybe many fields exist here like address, card number, etc.
        val tel: String     by map // copy of tel
    }
    
    fun PersonForm.toPersonRecord() = PersonRecord(HashMap(map).apply {
        this["name"] = "${remove("firstName")} ${remove("lastName")}"
    })
    


  • 这篇关于将Kotlin数据对象映射到数据对象的更好方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆