しおしお

IntelliJ IDEAのことなんかを書いてます

kotlinx.serializationつかってJSONのシリアライズ・デシリアライズしてみたよ

kotlinx.serialization を使って、JSONシリアライズとデシリアライズを試してみたよ。

シリアライズ&シリアライズ

JSONに対応するdata classを作成する

JSONに対応するdata classには、@Serializableを設定してあげます。ネストオブジェクトに対応するクラスにも同様に@Serializableが必要になります。

@Serializable
data class Test(
    val str: String,
    val int: Int,
    val double: Double,
    val nullable: String?,
    val defaultValue: String = "default",
    val lists: List<Nest>,
)

@Serializable
data class Nest(
    val value: String
)

シリアライズを試してみる

コード

Json.encodeToStringシリアライズが行えます。

val test = Test("str", 100, 9.9, null, "value", listOf(Nest("1"), Nest("2")))
val json = Json.encodeToString(test)
println("json = ${json}")

実行結果

ネストオブジェクト構造やnullもシリアライズできていることが確認できます。

json = {"str":"str","int":100,"double":9.9,"nullable":null,"defaultValue":"value","lists":[{"value":"1"},{"value":"2"}]}

シリアライズを試してみる

Json.decodeFromStringでデシリアライズが行なえます。JSONに対応するdata classは型パラメータとして指定してあげます。

コード

val test = Json.decodeFromString<Test>(
    """
        {
        "str": "str",
        "int": 100,
        "double": 99.99,
        "nullable": null,
        "lists": [
              {"value": "1"},
              {"value": "2"}
            ]
        }
    """.trimIndent()
)
println("test = ${test}")

実行結果

ネストオブジェクト構造もデシリアライズできていることが確認できます。 また、デフォルト値をしているdefaultValueプロパティは、JSON内にキーが存在していないため自動的にdefaultで指定されている値が設定されていることも確認できます。

test = Test(str=str, int=100, double=99.99, nullable=null, defaultValue=default, lists=[Nest(value=1), Nest(value=2)])

設定をカスタマイズしてみる

data classに未定義のキーが存在していた場合の動作を変更する

コード

ignoreUnknownKeystrueに設定することで、data class上に未定義のキーがJSONに存在していても無視して動作するようになります。 なお、この設定値をfalse(デフォルト値がfalse)にした場合は、実行時にkotlinx.serialization.json.internal.JsonDecodingExceptionが送出されます。 (例外メッセージの中で、ignoreUnknownKeystrueにしなよと親切に教えてくれます。)

@Serializable
data class Test(val value: String)

fun main() {
    val json = Json {
        ignoreUnknownKeys = true
    }
    val test = json.decodeFromString<Test>("""{"value": "a", "unknown": null}""")
    println("test = ${test}")
}

実行結果

data class上に存在しているキーのみデシリアライズされていることが確認できます。

test = Test(value=a)

JSONのキー名を指定する

コード

シリアライズ時のキー名は、@SerialNameで指定します。デシリアライズ時のキー名は@JsonNamesで指定します。

@Serializable
data class Test(
    @JsonNames("key") @SerialName("key") val value: String)

fun main() {
    val jsonString = Json.encodeToString(Test("ほげ"))
    println("json = ${jsonString}")
    val test = Json.decodeFromString<Test>(jsonString)
    println("test = ${test}")
}

実行結果

プロパティ名ではなくアノテーションで指定したキー名でやり取りできていることが確認できます。

json = {"key":"ほげ"}
test = Test(value=ほげ)

Date and Time APIのクラスを使ってみる

デフォルトでは、Date and Time APIのクラス群の変換には対応していないので、Serializerを作ることで対応してあげる必要があります。

Serializerの実装

KSerializer を実装して、シリアライズとデシリアライズの実装をしてあげます。 この例では、LocalDateを文字列変換してシリアライズし、デシリアライズではその逆を行っています。

object LocalDateSerializer: KSerializer<LocalDate> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING)

    override fun deserialize(decoder: Decoder): LocalDate {
        return LocalDate.parse(decoder.decodeString(), DateTimeFormatter.ISO_DATE)
    }

    override fun serialize(encoder: Encoder, value: LocalDate) {
        encoder.encodeString(value.format(DateTimeFormatter.ISO_DATE))
    }

}

シリアライズ・デシリアライズの実装

Serializerを、serializersModuleに登録してあげます。登録時には、変換対象のKClassとそれに対応するSerializer形式で登録します。

@Serializable
data class DateTime(
    @Contextual
    val date: LocalDate
)

fun main() {

    val json = Json {
        serializersModule = SerializersModule {
            contextual(LocalDate::class, LocalDateSerializer)
        }
    }
    val jsonString = json.encodeToString(DateTime(LocalDate.now()))
    println("jsonString = ${jsonString}")

    val dateTime = json.decodeFromString<DateTime>(jsonString)
    println("dateTime = ${dateTime}")
}

実行結果

自作したSerializerを使ったシリアライズとデシリアライズが動いていることが確認できますね。

jsonString = {"date":"2021-06-16"}
dateTime = DateTime(date=2021-06-16)