Kotlin Reified Type Parametersで汎用的なJsonTransformerを実装する

こんにちは。ASKULのほかほかごはんです。 今回はKotlinのユニークな機能、Reified Type Parametersの話とLOHACOでの活用事例の紹介をします。

JVM のジェネリクスについて

Javaではおなじみですが、JVMにおけるジェネリクスは型消去 (type erasure) で実装されており ジェネリッククラスの型引数は実行時には消滅してしまいます。これはKotlinでも同様です。

そのため、ジェネリック関数を呼び出すときにその関数の呼び出しについていた型引数を特定することはできません。

Kotlinの具象型パラメータについて

しかし、Kotlinでは関数をinlineとして宣言し、型パラメータをreifiedでマークした場合に型引数を具象化することができます。つまり、実行時に実際の型引数を参照できるようになります。

inline fun <reified T> coolMethod(value: Any) {
    if (value is T) {  // valueがTのインスタンスかどうか確認できたり
        println("$value is ${T::class.java} :-)") // T::class.javaとかできます
    } else {
        throw IllegalArgumentException("error")
    }
}

coolMethod<String>("foo") // -> foo is class java.lang.String :-)
coolMethod<String>(123)   // -> IllegalArgumentException

このように、型引数を利用したメソッドを書くことができます。

Moshiを利用して汎用的なJsonTransformerを実装する

もう少し実用的な使用法について紹介します。

LOHACOでは今年からサーバサイドアプリケーションの開発言語にKotlinを採用し始めています。

開発したWeb APIの1つにJsonライブラリとしてMoshiを利用したものがあり、 MoshiでJsonのシリアライズやパースをする際にReified Type Parametersを活用しています。

以下のクラスをMoshiでJsonシリアライズするケースを考えてみましょう。

data class TestResponse(
    val id: String,
    val name: String
)

リフレクションベースでシンプルに実装すると以下のようになります。

val res = TestResponse("1", "テスト")

val adapter = Moshi.Builder().add(KotlinJsonAdapterFactory()).build().adapter(TestResponse::class.java)
val json = adapter.toJson(res) // -> {"id":"1","name":"テスト"}

できました。しかし、このままではTestResponseクラスしかシリアライズできません。 例えばエラー時に返却するErrorResponseクラスもシリアライズしたくなったとしたら、新たにadapterを作成する必要があります。

ここでReified Type Parametersを活用します。 すべてのクラスをJsonシリアライズする汎用的な関数を実装しましょう。

object JsonTransformer {
    val moshi: Moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()

    inline fun<reified T> toJson(model: T): String = moshi.adapter(T::class.java).toJson(model)
}

型引数Tを具象化して、T::class.javaをadapterに渡します。

さあ、準備が整いました。早速使ってみましょう。

val res = TestResponse("1", "テスト")
val err = ErrorResponse("Parameter Error")

val json1 = JsonTransformer.toJson(res) // -> {"id":"1","name":"テスト"}

// もちろんCollectionもOK
val json2 = JsonTransformer.toJson(listOf(res)) // -> [{"id":"1","name":"テスト"}]

val errorJson = JsonTransformer.toJson(err) // -> {"message":"Parameter Error"}

もちろんparserも実装できます。

inline fun<reified T> fromJson(json: String): T? = moshi.adapter(T::class.java).fromJson(json)

val responses: List<TestResponse>? = JsonTransformer.fromJson("[{\"id\":\"1\",\"name\":\"テスト\"}]")
println(responses) // -> [{id=1, name=テスト}]

やったね!

まとめ

Reified Type Parametersを活用してあらゆるクラスに利用できるJsonTransformerを実装できました。
社内では真なるJsonTransformer と呼ばれています :-)

これからもKotlinの記事を増やしていきたいと思います。よろしくお願い致します。

追記

Moshiの1.6にてMoshi codegenがリリースされ、JsonAdapterをannotation processorで自動生成できるようになりました。 リフレクションのオーバーヘッドを気にする必要もなくなります。 詳細については公式を参照ください。 こちらも機会があれば試していきたいと思います。

ASKUL Engineering BLOG

2021 © ASKUL Corporation. All rights reserved.