【Kotlin】Javaを前提としたリフレクションによるマッピングライブラリは基本的にKotlinで動かない

`ModelMapper`など、「`Java`向けのリフレクションを利用したマッピングライブラリが`Kotlin`の`data class`に対して機能しない」という話をしばしば見かけるので、何故そうなるのかと、それに対応するためにどのような手段が有るかを書きます。

# TL;DR
- `Java`向けのマッピングライブラリは「引数無しコンストラクタでインスタンス生成 -> 個々のフィールドを初期化」という手順を前提としている
- 一方、`Kotlin`の`data class`は基本的に引数無しコンストラクタを持たない
- このため、`Java`向けのリフレクションを利用したマッピングライブラリは`Kotlin`の`data class`に対して機能しない

## 補足
この記事では、マッピングライブラリという言葉を以下のような意味で使います。

- マッピングライブラリ: 何らかのソースからフィールドを初期化したオブジェクトインスタンスを生成するような機能を提供するライブラリ
  - e.g. `ModelMapper`, `BeanPropertyRowMapper`

また、`Kotlin`上のマップ先は`class`でも`data class`でも関係有りませんが、`data class`になる場合が多いだろうということで、この記事内では`data class`を前提として扱います。

# 本文
- デコンパイルしてみる
- 無理やりでも動かす
- `Kotlin`に対応したツールについて

## デコンパイルしてみる
「`data class`は引数無しコンストラクタを持たない」を確認するため、以下のクラスを`idea`の機能でデコンパイルしてみます。

```kotlin
data class Sample(val foo: Int, val bar: Int?)
```

結果は以下のようになります。
引数無しコンストラクタの定義が無いことが分かります。

```java:デコンパイル結果（※記事内容に関係の無い部分は省略済み）

/* 略 */

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`安全は壊れるものの、見かけ上は従来のライブラリが機能するようになります。

- [Compiler Plugins \- Kotlin Programming Language](https://kotlinlang.org/docs/reference/compiler-plugins.html#no-arg-compiler-plugin)

### 引数無しコンストラクタをKotlin上に書く
マップ先クラスを`Java`の`POJO`っぽく機能するように書けば従来のライブラリが機能するようになります。
また、外部に公開するインターフェースと実際のデータを入れるクラスの実装を分離すれば、「外から見る限り普通の`data class`っぽく見える」状況は実現できます。

完全な`Kotlin`化を諦めるなら、`POJO`で実装した上でゲッターに`Nullability`を表すアノテーションを付けるというのも手です。

- [【Kotlin】NotNull\(NonNull\)/NullableアノテーションでJavaのフィールドをKotlinからNull安全に扱う【Java】 \- Qiita](https://qiita.com/wrongwrong/items/35d4849e9dfecb0ff977)

## Kotlinに対応したライブラリについて
最後に、自分が把握している限りで`Kotlin`に対応した`Object to Object`マッピングライブラリに関して書きます。

### KMapper
筆者が作成した、`Kotlin`のリフレクションによる関数呼び出しベースのマッピングライブラリです。
`Object`以外にも、`Map`などを引数に取ったり、複数引数からマッピングを行うことができます。

- [ProjectMapK](https://github.com/ProjectMapK)
  - [KMapper/README\.ja\.md at master · ProjectMapK/KMapper](https://github.com/ProjectMapK/KMapper/blob/master/README.ja.md)

### 自作する
`Kotlin`のリフレクションによる関数呼び出しは割と簡単に実装できるため、機能性を求めなければサクッと自作してしまうのが簡単という気がしています。

### MapStruct
記事執筆時点ではベータ版ですが、`MapStruct`は`1.4`からコンストラクタ呼び出しによるマッピングをサポートするそうです。

- [Support for constructors, Gradle incremental compilation and much more: MapStruct 1\.4\.0\.Beta1 is out – MapStruct](https://mapstruct.org/news/2020-06-01-mapstruct-1_4_0_Beta1-is-out-with-constructor-support/#making-use-of-constructor-arguments-when-instantiating-mapping-targets)

補足として、詳しい話は省きますが、`annotation-processor`ベースのライブラリであれば`Java`向けのものでも原理的に`Kotlin`対応がしやすいのかなと思っています。

### Jackson
`Jackson`には`Kotlin`サポートが有るため、これによって「1度`JSON`化する -> デシリアライズする」という手順でマッピングが実現できます。
機能は非常に充実していますし、シリアライズさえできれば多様なソースにも対応できることから、実行速度を除けば`Jackson`を用いるのが案外よいかもしれません。

---

この記事が英訳されて無断転載されていることを確認したため、各所に問い合わせ中です。
52e58e2f6d8b414c064258bf38fefe10c232a07a63263d33b2f26352cf22be8a