ModelMapper
など、「Java
向けのリフレクションを利用したマッピングライブラリがKotlin
のdata class
に対して機能しない」という話をしばしば見かけるので、何故そうなるのかと、それに対応するためにどのような手段が有るかを書きます。
TL;DR
-
Java
向けのマッピングライブラリは「引数無しコンストラクタでインスタンス生成 -> 個々のフィールドを初期化」という手順を前提としている - 一方、
Kotlin
のdata class
は基本的に引数無しコンストラクタを持たない - このため、
Java
向けのリフレクションを利用したマッピングライブラリはKotlin
のdata class
に対して機能しない
補足
この記事では、マッピングライブラリという言葉を以下のような意味で使います。
- マッピングライブラリ: 何らかのソースからフィールドを初期化したオブジェクトインスタンスを生成するような機能を提供するライブラリ
- e.g.
ModelMapper
,BeanPropertyRowMapper
- e.g.
また、Kotlin
上のマップ先はclass
でもdata class
でも関係有りませんが、data class
になる場合が多いだろうということで、この記事内ではdata class
を前提として扱います。
本文
- デコンパイルしてみる
- 無理やりでも動かす
-
Kotlin
に対応したツールについて
デコンパイルしてみる
「data class
は引数無しコンストラクタを持たない」を確認するため、以下のクラスをidea
の機能でデコンパイルしてみます。
data class Sample(val foo: Int, val bar: Int?)
結果は以下のようになります。
引数無しコンストラクタの定義が無いことが分かります。
/* 略 */
public final class Sample {
private final int foo;
@Nullable
private final Integer bar;
public final int getFoo() {
return this.foo;
}
@Nullable
public final Integer getBar() {
return this.bar;
}
public Sample(int foo, @Nullable Integer bar) {
this.foo = foo;
this.bar = bar;
}
/* 略 */
無理やりでも動かす
ここまでで説明した通り、Java
向けのリフレクションを利用したマッピングライブラリはKotlin
のdata class
に対して機能しません。
ただ、無理やりにでも動かす方法は幾つか有ります。
No-arg compiler pluginを使う
Kotlin
公式で、実行時にのみ呼び出せる引数無しコンストラクタを追加するプラグインが提供されています。
これを用いることで、null
安全は壊れるものの、見かけ上は従来のライブラリが機能するようになります。
引数無しコンストラクタをKotlin上に書く
マップ先クラスをJava
のPOJO
っぽく機能するように書けば従来のライブラリが機能するようになります。
また、外部に公開するインターフェースと実際のデータを入れるクラスの実装を分離すれば、「外から見る限り普通のdata class
っぽく見える」状況は実現できます。
完全なKotlin
化を諦めるなら、POJO
で実装した上でゲッターにNullability
を表すアノテーションを付けるというのも手です。
Kotlinに対応したライブラリについて
最後に、自分が把握している限りでKotlin
に対応したObject to Object
マッピングライブラリに関して書きます。
KMapper
筆者が作成した、Kotlin
のリフレクションによる関数呼び出しベースのマッピングライブラリです。
Object
以外にも、Map
などを引数に取ったり、複数引数からマッピングを行うことができます。
自作する
Kotlin
のリフレクションによる関数呼び出しは割と簡単に実装できるため、機能性を求めなければサクッと自作してしまうのが簡単という気がしています。
MapStruct
記事執筆時点ではベータ版ですが、MapStruct
は1.4
からコンストラクタ呼び出しによるマッピングをサポートするそうです。
補足として、詳しい話は省きますが、annotation-processor
ベースのライブラリであればJava
向けのものでも原理的にKotlin
対応がしやすいのかなと思っています。
Jackson
Jackson
にはKotlin
サポートが有るため、これによって「1度JSON
化する -> デシリアライズする」という手順でマッピングが実現できます。
機能は非常に充実していますし、シリアライズさえできれば多様なソースにも対応できることから、実行速度を除けばJackson
を用いるのが案外よいかもしれません。
この記事が英訳されて無断転載されていることを確認したため、各所に問い合わせ中です。
52e58e2f6d8b414c064258bf38fefe10c232a07a63263d33b2f26352cf22be8a