Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/346.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 将Kotlin数据对象映射到数据对象的更好方法_Java_Kotlin_Modelmapper - Fatal编程技术网

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

Java 将Kotlin数据对象映射到数据对象的更好方法,java,kotlin,modelmapper,Java,Kotlin,Modelmapper,我想将一些“数据”类对象转换/映射为类似的“数据”类对象。例如,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 ...

我想将一些“数据”类对象转换/映射为类似的“数据”类对象。例如,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示例:)

如何在Kotlin中映射这些“数据”对象

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

更新: 每个类的用途取决于应用程序的类型,但它们可能位于应用程序的不同层

例如:

  • 从数据库(数据库实体)到HTML表单(模型/视图模型)的数据
  • 将REST API结果转换为数据库的数据
这些类别相似,但不相同

出于以下原因,我希望避免正常的函数调用:

  • 这取决于论点的顺序。一个类的函数包含许多具有相同类型(如字符串)的字段,很容易被破坏
  • 许多声明都是必需的,尽管大多数映射可以通过命名约定来解决

当然,我们希望有一个具有类似功能的库,但也欢迎提供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)

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

data class PersonForm(
    val firstName: String,
    val lastName: String,
    val age: Int,
    val tel: String
) {
    val name = "${firstName} ${lastName}"
}
  • 最简单(最好?):

  • 反思(不是很好的表现):

  • 缓存反射(性能正常,但速度不如#1):

    开放式变压器
    受保护的构造函数(包括类:KClass,外类:KClass){
    private val outConstructor=outClass.primaryConstructor!!
    lazy提供的Properties ByName中的私有值{
    inClass.memberProperties.associateBy{it.name}
    }
    有趣的转换(数据:T):R=with(outConstructor){
    callBy(parameters.associate{parameter->
    参数到argFor(参数,数据)
    })
    }
    open fun argFor(参数:KParameter,数据:T):有吗{
    返回inPropertiesByName[参数.name]?.get(数据)
    }
    }
    val personFormToPersonRecordTransformer=对象
    :Transformer(PersonForm::class,PersonRecord::class){
    覆盖参数(参数:KParameter,数据:PersonForm):有吗{
    返回时间(parameter.name){
    “name”->带有(数据){“$firstName$lastName”}
    else->super.argFor(参数、数据)
    }
    }
    }
    fun PersonForm.toPersonRecord()=personFormToPersonRecordTransformer.transform(此)
    
  • 数据类PersonForm(val-map:map){
    val firstName:映射字符串
    val lastName:按映射的字符串
    val年龄:按地图整型
    //这里可能有很多字段,如地址、卡号等。
    瓦尔电话:按地图排列
    }
    //映射到。。。
    数据类PersonRecord(val映射:映射){
    val name:String by map/“${firstName}${lastName}”
    val age:Int by map//age的副本
    //这里可能有很多字段,如地址、卡号等。
    val tel:按地图字符串//tel的副本
    }
    fun PersonForm.toPersonRecord()=PersonRecord(HashMap(map).apply{
    此[“名称”]=“${remove(“firstName”)}${remove(“lastName”)}”
    })
    
  • 这项工作使用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()
        )
    
    inline fun Any.mapTo():T=
    GsonBuilder().create().run{
    toJson(this@mapTo).let{fromJson(it,T::class.java)}
    }
    有趣的PersonForm.toRecord():PersonRecord=
    mapTo().copy(
    name=“$firstName$lastName”
    )
    有趣的PersonRecord.toForm():PersonForm=
    mapTo().copy(
    firstName=name.split(“”.first(),
    lastName=name.split(“”.last()
    )
    

    由于Gson使用..

    MapStruct,因此不允许将不可为null的值设为null,这样kapt就可以生成进行映射的类(无反射)

    使用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) (...)
    
    使用:

    编辑:

    现在使用@KotlinBuilder来避免构造函数()问题:

    使用
    @KotlinBuilder
    注释数据类。这将创建一个MapStruct使用的
    PersonBuilder
    类,从而避免破坏数据类与构造函数()的接口

    依赖关系:

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

    您可以使用ModelMapper映射到Kotlin数据类。关键是:

    • 使用@JVM重载(生成不带参数的构造函数)
    • 数据类成员的默认值
    • 可变成员,var而不是val

      数据类AppSyncEvent@JVM重载构造函数(
      变量字段:String=“”,
      变量参数:Map=mapOf(),
      变量源:Map=mapOf()
      )
      val event=ModelMapper().map(请求,AppSyncEvent::class.java)
      

    对于您可以使用的ModelMapper,您可以使用它创建一个注释来标记您的数据类,从而为使用反射的库获取一个合成的无参数构造函数。您的数据类需要使用
    var
    而不是
    val

    package com.example
    
    annotation class NoArg
    
    @NoArg
    data class MyData(var myDatum: String)
    
    mm.map(. . ., MyData::class.java)
    
    在build.gradle中(参见Maven文档):

    使用

    如果字段名不同,则可能需要一些映射规则。见

    PS:使用
    kotlin no args
    plugin使数据类具有默认的no arg构造函数

    您编写的操作是我想要做的。但是我想减少映射声明,因为存在许多具有相同名称的字段(如tel->tel)。我只想写一些特殊的规则,比如firstName+lastName=>name。请描述一下如何使用映射类。拥有两种不同数据格式的目的是什么?从未听说过数据模型的重复(遗留代码的情况除外)。通常,您正在处理的数据
    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()
        )
    
    @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) (...)
    
    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)
    
    @KotlinBuilder
    data class Person(
        val firstName: String,
        val lastName: String,
        val age: Int,
        val tel: String
    )
    
    // 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")
    
    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)
    
    package com.example
    
    annotation class NoArg
    
    @NoArg
    data class MyData(var myDatum: String)
    
    mm.map(. . ., MyData::class.java)
    
    buildscript {
      . . .
      dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
      }
    }
    
    apply plugin: 'kotlin-noarg'
    
    noArg {
      annotation "com.example.NoArg"
    }
    
    /** 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)