我想转换/映射一些"数据"类对象类似"数据"类对象。例如,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
)
我在Java中使用ModelMapper进行此类工作,但由于数据类是最终的(ModelMapper创建CGLib代理以读取映射定义),因此无法使用它。当我们打开这些类/字段时,我们可以使用ModelMapper,但是我们必须实现" data"的功能。手动上课。 (参见ModelMapper示例:https://github.com/jhalterman/modelmapper/blob/master/examples/src/main/java/org/modelmapper/gettingstarted/GettingStartedExample.java)
如何映射此类"数据"科特林的物品?
更新 ModelMapper自动映射具有相同名称的字段(如tel - > tel),而不映射声明。我想用Kotlin的数据类来做。
更新 每个类的目的取决于应用程序的类型,但这些应用程序可能放在应用程序的不同层中。
例如:
这些类很相似,但不一样。
出于以下原因,我想避免正常的函数调用:
当然,有一个具有类似功能的库,但也欢迎Kotlin功能的信息(如在ECMAScript中传播)。
答案 0 :(得分:33)
最简单(最好?):
td
反思(表现不佳):
fun PersonForm.toPersonRecord() = PersonRecord(
name = "$firstName $lastName",
age = age,
tel = tel
)
缓存反射(性能良好但不如#1快):
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)
}
})
}
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)
答案 1 :(得分:15)
这是你在寻找吗?
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)
答案 2 :(得分:3)
你真的想要一个单独的课吗?您可以向原始数据类添加属性:
data class PersonForm(
val firstName: String,
val lastName: String,
val age: Int,
val tel: String
) {
val name = "${firstName} ${lastName}"
}
答案 3 :(得分:3)
使用MapStruct:
@Mapper
interface PersonConverter {
@Mapping(source = "phoneNumber", target = "phone")
fun convertToDto(person: Person) : PersonDto
@InheritInverseConfiguration
fun convertToModel(personDto: PersonDto) : Person
}
使用:
val converter = Mappers.getMapper(PersonConverter::class.java) // or PersonConverterImpl()
val person = Person("Samuel", "Jackson", "0123 334466", LocalDate.of(1948, 12, 21))
val personDto = converter.convertToDto(person)
println(personDto)
val personModel = converter.convertToModel(personDto)
println(personModel)
https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-kotlin
答案 4 :(得分:1)
/** Util.kt **/
class MapperDto() : ModelMapper() {
init {
configuration.matchingStrategy = MatchingStrategies.LOOSE
configuration.fieldAccessLevel = Configuration.AccessLevel.PRIVATE
configuration.isFieldMatchingEnabled = true
configuration.isSkipNullEnabled = true
}
}
object Mapper {
val mapper = MapperDto()
inline fun <S, reified T> convert(source: S): T = mapper.map(source, T::class.java)
}
用法
val form = PersonForm(/** ... **/)
val record: PersonRecord = Mapper.convert(form)
如果字段名称不同,则可能需要一些映射规则。参见the getting started
PS:使用kotlin no-args
插件将默认no-arg构造函数与数据类一起使用
答案 5 :(得分:0)
这可以使用Gson:
inline fun <reified T : Any> Any.mapTo(): T =
GsonBuilder().create().run {
toJson(this@mapTo).let { fromJson(it, T::class.java) }
}
fun PersonForm.toRecord(): PersonRecord =
mapTo<PersonRecord>().copy(
name = "$firstName $lastName"
)
fun PersonRecord.toForm(): PersonForm =
mapTo<PersonForm>().copy(
firstName = name.split(" ").first(),
lastName = name.split(" ").last()
)
由于Gson使用sun.misc.Unsafe .. ,因此不允许空值的为空
答案 6 :(得分:0)
您可以使用ModelMapper映射到Kotlin数据类。关键是:
可变成员,用var代替val
public class TimestampFinder { private static final long SOME_CONSTANT = 10_000; private List<Instant> timestamps = ... ; // initialize and sort public Instant find(Instant inputTs) { int index = Collections.binarySearch(timestamps, inputTs, (t, key) -> { if (t.isBefore(key) && key.isBefore(t.plusMillis(SOME_CONSTANT))) { // inputTs is part of the duration after t // return 0 to indicate that we've found a match return 0; } // inputTs not in interval // use Instant's implementation of Comparator to indicate to binarySearch if it should continue the search in the upper or lower half of the list return t.compareTo(key); }); return index >= 0 ? timestamps.get(index) : null; } }
答案 7 :(得分:0)
对于ModelMapper,您可以使用Kotlin's no-arg compiler plugin,使用它可以创建标记您的数据类的注释,以获取使用反射的库的合成no-arg构造函数。您的数据类需要使用var
而不是val
。
package com.example
annotation class NoArg
@NoArg
data class MyData(var myDatum: String)
mm.map(. . ., MyData::class.java)
和build.gradle中(请参阅Maven文档):
buildscript {
. . .
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}
apply plugin: 'kotlin-noarg'
noArg {
annotation "com.example.NoArg"
}