Java 将 Kotlin 数据对象映射到数据对象的更好方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/39199426/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Better way to map Kotlin data objects to data objects
提问by sunnyone
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.
我想将一些“数据”类对象转换/映射到类似的“数据”类对象。例如,用于 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
)
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)
我在 Java 中使用 ModelMapper 进行此类工作,但无法使用它,因为数据类是最终的(ModelMapper 创建 CGLib 代理来读取映射定义)。当我们打开这些类/字段时,我们可以使用ModelMapper,但是我们必须手动实现“数据”类的特性。(参见 ModelMapper 示例:https: //github.com/jhalterman/modelmapper/blob/master/examples/src/main/java/org/modelmapper/gettingstarted/GettingStartedExample.java)
How to map such "data" objects in Kotlin?
如何在 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.
更新:ModelMapper 自动映射具有相同名称的字段(如 tel -> tel)而无需映射声明。我想用 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.
更新:每个类的目的取决于什么样的应用程序,但这些很可能被放置在一个应用程序的不同层中。
For example:
例如:
- data from database (Database Entity) to data for HTML form (Model/View Model)
- REST API result to data for database
- 从数据库(Database Entity)到 HTML 表单数据(模型/视图模型)的数据
- REST API 结果到数据库的数据
These classes are similar, but are not the same.
这些类很相似,但又不一样。
I want to avoid normal function calls for these reasons:
由于以下原因,我想避免正常的函数调用:
- It depends on the order of arguments. A function for a class with many fields that have same type (like String) will be broken easily.
- Many declarations are nesessary though most mappings can be resolved with naming convention.
- 这取决于参数的顺序。具有许多具有相同类型的字段(如 String)的类的函数很容易被破坏。
- 许多声明是必需的,尽管大多数映射可以通过命名约定来解决。
Of course, a library that has similar feature is intended, but information of the Kotlin feature is also welcome (like spreading in ECMAScript).
当然,一个具有类似功能的库是有意的,但也欢迎提供 Kotlin 功能的信息(例如在 ECMAScript 中传播)。
回答by klimat
Is this are you looking for?
这是你要找的吗?
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)
}
}
and then:
进而:
val personRecord = PersonRecord.ModelMapper.from(personForm)
回答by voddan
Do you really want a separate class for that? You can add properties to the original data class:
你真的想要一个单独的类吗?您可以向原始数据类添加属性:
data class PersonForm(
val firstName: String,
val lastName: String,
val age: Int,
val tel: String
) {
val name = "${firstName} ${lastName}"
}
回答by mfulton26
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) } }) }
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")}" })
最简单(最好?):
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")}" })
回答by Tom Power
This works using Gson:
这使用 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()
)
with not nullable values allowed to be null because Gson uses sun.misc.Unsafe..
不可为空的值允许为空,因为 Gson 使用sun.misc.Unsafe..
回答by Евгений Иванов
MapStruct lets kapt generate classes doing the mapping (without reflection).
MapStruct 让 kapt 生成执行映射的类(无反射)。
Use MapStruct:
使用地图结构:
@Mapper
interface PersonConverter {
@Mapping(source = "phoneNumber", target = "phone")
fun convertToDto(person: Person) : PersonDto
@InheritInverseConfiguration
fun convertToModel(personDto: PersonDto) : Person
}
// Note this either needs empty constructor or we need @KotlinBuilder as dsecribe below
data class Person: this(null, null, null, null) (...)
Use:
用:
val converter = Mappers.getMapper(PersonConverter::class.java) // or PersonConverterImpl()
val person = Person("Samuel", "Hymanson", "0123 334466", LocalDate.of(1948, 12, 21))
val personDto = converter.convertToDto(person)
println(personDto)
val personModel = converter.convertToModel(personDto)
println(personModel)
Edit:
编辑:
Now with @KotlinBuilder for avoiding constructor() issue:
现在使用@KotlinBuilder 来避免构造函数() 问题:
GitHub: Pozo's mapstruct-kotlin
GitHub: Pozo 的 mapstruct-kotlin
Annotate data classes with @KotlinBuilder
. This will create a PersonBuilder
class, which MapStruct uses, thus we avoid ruining the interface of the data class with a constructor().
用 注释数据类@KotlinBuilder
。这将创建一个PersonBuilder
MapStruct 使用的 类,因此我们避免使用构造函数()破坏数据类的接口。
@KotlinBuilder
data class Person(
val firstName: String,
val lastName: String,
val age: Int,
val tel: String
)
Dependency :
依赖:
// https://mvnrepository.com/artifact/com.github.pozo/mapstruct-kotlin
api("com.github.pozo:mapstruct-kotlin:1.3.1.1")
kapt("com.github.pozo:mapstruct-kotlin-processor:1.3.1.1")
https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-kotlin
https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-kotlin
回答by Ken
You can use ModelMapper to map to a Kotlin data class. The keys are:
您可以使用 ModelMapper 映射到 Kotlin 数据类。关键是:
- Use @JvmOverloads (generates a constructor with no arguments)
- Default values for data class member
Mutable member, var instead of val
data class AppSyncEvent @JvmOverloads constructor( var field: String = "", var arguments: Map<String, *> = mapOf<String, Any>(), var source: Map<String, *> = mapOf<String, Any>() ) val event = ModelMapper().map(request, AppSyncEvent::class.java)
- 使用@JvmOverloads(生成一个没有参数的构造函数)
- 数据类成员的默认值
可变成员,var 而不是 val
data class AppSyncEvent @JvmOverloads constructor( var field: String = "", var arguments: Map<String, *> = mapOf<String, Any>(), var source: Map<String, *> = mapOf<String, Any>() ) val event = ModelMapper().map(request, AppSyncEvent::class.java)
回答by Mike Placentra
For ModelMapper you could use Kotlin's no-arg compiler plugin, with which you can create an annotation that marks your data class to get a synthetic no-arg constructor for libraries that use reflection. Your data class needs to use var
instead of val
.
对于 ModelMapper,您可以使用Kotlin 的 no-arg 编译器插件,您可以使用它创建一个注释来标记您的数据类,以便为使用反射的库获取合成的 no-arg 构造函数。您的数据类需要使用var
而不是val
.
package com.example
annotation class NoArg
@NoArg
data class MyData(var myDatum: String)
mm.map(. . ., MyData::class.java)
and in build.gradle (see docs for Maven):
并在 build.gradle 中(参见 Maven 文档):
buildscript {
. . .
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}
apply plugin: 'kotlin-noarg'
noArg {
annotation "com.example.NoArg"
}
回答by zack evein
Using ModelMapper
使用模型映射器
/** 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)
}
Usage
用法
val form = PersonForm(/** ... **/)
val record: PersonRecord = Mapper.convert(form)
You might need some mapping rules if the field names differ. See the getting started
PS: Use kotlin no-args
plugin for having default no-arg constructor with your data classes
如果字段名称不同,您可能需要一些映射规则。请参阅入门
PS:使用kotlin no-args
插件为您的数据类提供默认的无参数构造函数