将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)
-
最简单(最好?):
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)
-
在地图中存储属性
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}" }