Kotlinでのデータオブジェクトのマッピング
1. イントロ
従来のコードベースを使用する場合、外部ライブラリを使用する場合、またはフレームワークと統合する場合、さまざまなオブジェクトまたはデータ構造間をマッピングするユースケースが定期的にあります。
このチュートリアルでは、組み込みのKotlin機能を使用してこの目標を簡単に達成する方法を見ていきます。
2. 簡単な拡張機能
次の例を使用してみましょう。クラスUserがあります。これは、コアドメインのクラスである可能性があります。 リレーショナルデータベースからロードするエンティティである可能性もあります。
data class User(
val firstName: String,
val lastName: String,
val street: String,
val houseNumber: String,
val phone: String,
val age: Int,
val password: String)
次に、このデータについて別のビューを提供したいと思います。 このクラスをUserViewと呼ぶことにしました。これは、Webコントローラーからの応答送信として使用されていると想像できます。 ドメイン内の同じデータを表していますが、一部のフィールドはUserクラスのフィールドの集合であり、一部のフィールドは単に異なる名前を持っています。
data class UserView(
val name: String,
val address: String,
val telephone: String,
val age: Int
)
ここで必要なのは、User→_UserView_をマップするマッピング関数です。 UserViewはアプリケーションの外層にあるため、この関数をUserクラスに追加したくありません。 また、UserViewオブジェクトを作成するために、Userクラスのカプセル化を解除し、ヘルパークラスを使用してUserオブジェクトにアクセスし、そのデータを引き出したくありません。
幸い、KotlinはExtension Functionsと呼ばれる言語機能を提供します。 Userクラスで拡張機能を定義し、定義したパッケージスコープ内でのみアクセス可能にすることができます。
fun User.toUserView() = UserView(
name = "$firstName $lastName",
address = "$street $houseNumber",
telephone = phone,
age = age
)
テスト内でこの関数を使用して、使用方法を理解してみましょう。
class UserTest {
@Test
fun `maps User to UserResponse using extension function`() {
val p = buildUser()
val view = p.toUserView()
assertUserView(view)
}
private fun buildUser(): User {
return User(
"Java",
"Duke",
"Javastreet",
"42",
"1234567",
30,
"s3cr37"
)
}
private fun assertUserView(pr: UserView) {
assertAll(
{ assertEquals("Java Duke", pr.name) },
{ assertEquals("Javastreet 42", pr.address) },
{ assertEquals("1234567", pr.telephone) },
{ assertEquals(30, pr.age) }
)
}
3. コトリン反射機能
上記の例は非常に単純です(したがって、ほとんどのユースケースに推奨されます)が、まだ定型的なコードが少し含まれています。 多数のフィールド(おそらく数百)を持つクラスがあり、それらのほとんどをターゲットクラスの同じ名前のフィールドにマップする必要がある場合はどうでしょうか。
この場合、マッピングコードのほとんどを記述しないように、Kotlin Reflection機能を使用することを検討できます。
リフレクションを使用したマッピング関数は次のようになります。
fun User.toUserViewReflection() = with(::UserView) {
val propertiesByName = User::class.memberProperties.associateBy { it.name }
callBy(parameters.associate { parameter ->
parameter to when (parameter.name) {
UserView::name.name -> "$firstName $lastName"
UserView::address.name -> "$street $houseNumber"
UserView::telephone.name -> phone
else -> propertiesByName[parameter.name]?.get([email protected])
}
})
}
Kotlinwith()関数を使用して、メソッド呼び出しレシーバーとしてUserViewデフォルトコンストラクターを使用しています。 with(),に提供されるラムダ関数内で、リフレクションを使用して、User::class.memberProperties.associateBy \{ it.name }を使用してメンバープロパティのMapを取得します(メンバー名をキー、メンバープロパティを値として)。
次に、カスタムパラメータマッピングを使用してUserViewコンストラクタを呼び出します。 ラムダ内では、whenキーワードを使用して条件付きマッピングを提供します。
興味深い事実は、単純なStringsの代わりにUserView::name.nameのように、リフレクションを使用して取得した実際のパラメーター名をマップできることです。 This means we can completely leverage the Kotlin compiler here、コードが壊れることを恐れずにリファクタリングの場合に役立ちます。
パラメーターの名前、住所、電話の特別なマッピングがありますが、他のすべてのフィールドにはデフォルトの名前ベースのマッピングを使用します。
リフレクションベースのアプローチは一見非常に興味深いように見えますが、これによりコードベースがさらに複雑になり、リフレクションを使用するとランタイムパフォーマンスに悪影響を及ぼす可能性があることに注意してください。
4. 結論
組み込みのKotlin言語機能を使用して、単純なデータマッピングのユースケースを簡単に解決できることを確認しました。 マッピングコードを手で書くことは単純なユースケースには適していますが、リフレクションを使用してより複雑なソリューションを書くこともできます。
すべてのコード例over on GitHubを見つけることができます。