2020-04-12に投稿

単一プロパティを持つdata classをJSONのプリミティブ型として表現する

Value Objectとして使っている、単一プロパティを持つdata classを、シンプルなJSONとして表現するのにちょっと手こずったのでまとめておきます。

問題設定

例として、次のようなクラス定義を考えます。

data class User(val userId: UserId, val userName: UserName)
data class UserId(val value: Int)
data class UserName(val value: String)

UserオブジェクトをJacksonでシリアライズした場合、普通だと次のようになります。

{"userId": {"value": 1}, "userName": {"value": "sato"}}

これを次のようにシリアライズした上で、元の形にデシリアライズしたいというのが、この記事の主題です。

{"userId": 1, "userName": "sato"}

なお、 UserIdUserNameUser 以外のクラスからも使われる可能性があります。これらを使うクラス間で挙動を統一できるよう、クラス定義に手を加えるなら User よりも UserIdUserName に加えたいです。

結論

次のように、 UserIdUserName にアノテーション @JsonCreator@JsonValue をつけると実現できます。

data class User(val userId: UserId, val userName: UserName)
data class UserId @JsonCreator(mode = JsonCreator.Mode.DELEGATING) constructor(@JsonValue val value: Int)
data class UserName @JsonCreator(mode = JsonCreator.Mode.DELEGATING) constructor(@JsonValue val value: String)

解説

まず、 @JsonValue はシリアライズに影響を与えるアノテーションで、シリアライズ結果を取得するメソッドに付けます。

次に、 @JsonCreator はデシリアライズに影響を与えるアノテーションで、JSONの値からオブジェクトを作るためのコンストラクターやファクトリーメソッドに付けます。Javaの場合はコンストラクターに @JsonCreator をつけるだけでいいのですが、Kotlin Moduleを使っている場合はうまく行かないので、 mode = JsonCreator.Mode.DELEGATING が必要です [1]。

なお、Kotlinでプライマリコンストラクターにアノテーションをつけるには、 constructor キーワードが必要です [2]。

コード

実際のコードは次のようになります。

build.gradleの一部

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    implementation "com.fasterxml.jackson.core:jackson-databind:2.9.10.4"
    implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.10"
}

Main.kt

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonValue
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue

fun main() {
    val u = User(UserId(1), UserName("sato"))
    println(u)

    val mapper = jacksonObjectMapper()
    val json = mapper.writeValueAsString(u)
    println(json)

    val parsed: User = mapper.readValue(json)
    println(parsed)
}

data class User(val userId: UserId, val userName: UserName)
data class UserId @JsonCreator(mode = JsonCreator.Mode.DELEGATING) constructor(@JsonValue val value: Int)
data class UserName @JsonCreator(mode = JsonCreator.Mode.DELEGATING) constructor(@JsonValue val value: String)

実行結果

User(userId=UserId(value=1), userName=UserName(value=sato))
{"userId":1,"userName":"sato"}
User(userId=UserId(value=1), userName=UserName(value=sato))

参考

注釈

ツイッターでシェア
みんなに共有、忘れないようにメモ

かと

Software Engineer / Python, Django, Kotlin, TypeScript、Reactなど / CIとかの自動化も好き / 著書:Pythonクローリング&スクレイピング https://t.co/AWV2hxdhKK / 発言は個人の見解であり所属組織を代表しません

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント