93

一部の「データ」クラス オブジェクトを同様の「データ」クラス オブジェクトに変換/マッピングしたいと考えています。たとえば、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 を使用していますが、データ クラスが final であるため使用できません (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のデータクラスでやりたい。

更新: 各クラスの目的はアプリケーションの種類によって異なりますが、これらはおそらくアプリケーションの別のレイヤーに配置されます。

例えば:

  • データベース(Database Entity)からHTMLフォーム用データ(Model/View Model)へ
  • データベースのデータへの REST API の結果

これらのクラスは似ていますが、同じではありません。

これらの理由から、通常の関数呼び出しを避けたい:

  • 引数の順序によって異なります。同じ型 (文字列など) を持つ多くのフィールドを持つクラスの関数は、簡単に壊れてしまいます。
  • ほとんどのマッピングは命名規則で解決できますが、多くの宣言が必要です。

もちろん同様の機能を持ったライブラリを想定していますが、Kotlinの機能の情報も歓迎です(ECMAScriptで拡散するなど)。

4

8 に答える 8

81
  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")}"
    })
    
于 2016-08-29T19:25:12.777 に答える
25

これはあなたが探しているものですか?

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)
于 2016-08-29T08:31:02.107 に答える
4

そのために本当に別のクラスが必要ですか?元のデータ クラスにプロパティを追加できます。

data class PersonForm(
    val firstName: String,
    val lastName: String,
    val age: Int,
    val tel: String
) {
    val name = "${firstName} ${lastName}"
}
于 2016-08-29T13:30:56.713 に答える