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

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
)

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

如何在Kotlin中映射此类“数据”对象?

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

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

例如:
  • 数据从数据库(数据库实体)到HTML格式的数据(模型/ View 模型)
  • REST API结果转换为数据库
  • 的数据

    这些类是相似的,但不相同。

    由于以下原因,我想避免正常的函数调用:
  • 它取决于参数的顺序。具有许多具有相同类型(例如String)的字段的类的函数将很容易被破坏。
  • 尽管大多数映射都可以使用命名约定来解析,但许多声明都是必需的。

  • 当然,可以使用具有类似功能的库,但是也欢迎使用Kotlin功能的信息(例如在ECMAScript中传播)。

    最佳答案

  • 最简单(最好?):
    fun PersonForm.toPersonRecord() = PersonRecord(
            name = "$firstName $lastName",
            age = age,
            tel = tel
    )
    
  • 反射(性能不佳):
    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快):
    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)
    
  • Storing Properties in a Map
    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")}"
    })
    
  • 10-07 13:03
    查看更多