将Kotlin数据对象映射到数据对象的更好方法

我想将一些“数据”类对象转换/映射到类似的“数据”类对象。 例如,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表单数据(模型/视图模型)
  • REST API结果到数据库的数据

这些类是相似的,但不一样。

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

  • 这取决于参数的顺序。 具有许多相同类型字段(如String)的类的函数将很容易被破坏。
  • 尽管大多数映射都可以通过命名约定来解决,但很多声明都是非常必要的。

当然,有一个类似功能的图书馆是有意的,但是Kotlin功能的信息也是受欢迎的(比如在ECMAScript中传播)。

这是你要找的吗?

 data class PersonRecord(val name: String, val age: Int, val tel: String){ object ModelMapper { fun from(form: PersonForm) = PersonRecord(form.firstName + form.lastName, form.age, form.tel) } } 

接着:

 val personRecord = PersonRecord.ModelMapper.from(personForm) 
  1. 最简单(最好?):

     fun PersonForm.toPersonRecord() = PersonRecord( name = "$firstName $lastName", age = age, tel = tel ) 
  2. 反思(不是很好的表现):

     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) } }) } 
  3. 缓存反射(性能不错,但速度不及#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) 
  4. 在地图中存储属性

     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")}" }) 

你真的想要一个单独的课程吗? 您可以将属性添加到原始数据类:

 data class PersonForm( val firstName: String, val lastName: String, val age: Int, val tel: String ) { val name = "${firstName} ${lastName}" }